Displaying field-dependent errors in the message line

From Try-AS/400
Revision as of 18:32, 18 March 2019 by PoC (talk | contribs) (Summary)
Jump to navigation Jump to search

It is always considered good habit to let a program's users know what's going on. Or what failed, and why. OS/400 provides components to relatively easily display field-dependent errors in the message line (at the bottom of the screens). Surely you already have seen such messages by other OS/400 components.

Probably you didn't notice that this message line is often a subfile. With one line, though, but you can place the cursor there and scroll through it's content with the appropriate Page-Up and Page-Down-Keys. If you ever wondered how to see other messages from one program invocation without bringing up the job log, now you know.

There are multiple ways to provide this functionality. I'm showing the way I considered the best for me. That's my choice and doesn't have to match yours. At least it should provide a starting point for your own experimentation.

We use the following ingredients:

  • Message Files,
  • Display Files (certain statements within, that is),
  • ILE RPG Code.

For the following examples, we assume an already existing application which should not show the standard error screen when an already locked record is encountered. Instead, it should show a message in the message line and skip the record. Your current library is assumed to be the library of that mentioned application.

Message Files

Message Files are special database files. Before elaborating, let's create a message file:

CRTMSGF MSGF(GENERICMSG) CCSID(*JOB)

Now, we populate this file with a message:

ADDMSGD MSGID(ERR1218) MSGF(*CURLIB/GENERICMSG) MSG('Record &1 is already in use.') SECLVL('Record &1 has been locked by another user for update. Wait until the lock will be released or use WRKOBJLCK OBJ(*CURLIB/MYPF) OBJTYPE(*FILE)  MBR(*FIRST) to find out who is locking. So you can contact the user in question to close that record.') SEV(10) FMT((*CHAR 10 0)) TYPE(*NONE) LEN(*NONE)

Noteworthy stuff:

  • The &1 in the message text is a variable which we'll use later to customize this message a bit before showing it to the user.
  • The MSG is to be shown in the message line. It should give a concise description of what's going on. The SECLVL text will be shown when the user positions the cursor in the message line and presses the HELP- or F1-Key. It should provide information what to do next to probably solve the issue.

There's a system wide message file QSYS/QCPFMSG, with proper localized texts. Problem with this file is that it contains hundreds of standard messages, so it is cumbersome to find the right one.

To change and view content of a message file, you can use:

WRKMSGD MSGF(GENERICMSG)

Message files enable the to have a proper place for probably longer text, because handling string literals in RPG is really cumbersome. Plus, with a message file, your application can be more easily localized for other languages without wading through all the RPG program code.

Display Files

Display Files define the screen appearance to the user and provide an interface in program code to get data in and out of this screen. The message line is usually part of that screen, and thus of a message file.

We could handle the entire message subfile ourselves. But we also could let OS/400 do that for us. This is accomplished by adding the ERRSFL Keyword in the global (top) section of any DSPF DDS file. I strongly recommend to include this statement per default.

The next steps are dependent on your Record Format types. That is, we need to differ between ordinary Record Formats with just one page, and Subfile Record Format, which always come in twos: The SFL record to describe one block[1] of data, and the SFLCTL which actually controls the behavior of the SFL itself. See List views: Subfiles for details.

For clarity, ordinary record formats will be called Details Record Formats[2] and Subfile Record Formats will be called Subfiles, unless we're referencing the SFL or SFLCTL itself.

To understand the next sections it is important that you understand that the shown DDS keywords always[3] tie messages to a certain field.

Automatic Error Handling


A somewhat automatic error handling can be established with the CHKMSGID keyword to a field. It must be used together with some predefined check provided by DDS statements, such as a VALUES option for a field which lists valid data to be entered into the field.

Often, this is used in a SFL for the OPT-Field, where the user enters (usually) decimal numbers to tell the system what to do with the chosen record. With this facility you can offer the user a better understandable error message then the standard text from OS/400. Example:

     A                                      ERRSFL
     A          R MYSFL                     SFL
     A            OPT            1A  I  9  3VALUES(' ' '2' '4' '5' '+' '-')
     A                                      CHKMSGID(SFL0001 GENERICMSG)

Again, noteworthy stuff:

  • SFL0001 is an example ID. There's no definition for it in the above example message file!
  • If the VALUES-Check of the corresponding field fails, the corresponding field will be automatically set to DSPATR(RI) (Reverse Image) to show the user where the error occurred.[4]

CHKMSGID is not limited to subfiles, and not limited to provide error messages for a range of valid entries!

Programmatic Error Handling


In contrast to the above, the next section applies when your program has to cope with other error conditions, for which OS/400 doesn't provide a proper handling procedure.

Details Record Format

This is a relatively easy one. See the following example minimal DDS excerpt:

     A                                      ERRSFL
     A          R MYDETAIL
     A            TYP       R        B  8 12
     A  91                                  ERRMSGID(ERR1218 GENERICMSG 91 &ERRSTR)
     A            ERRSTR        16A  P

Again, noteworthy stuff:

  • TYP is an ordinary field, referenced from a file not shown or otherwise referenced here for minimality.
  • The ERRMSG(ID) statement is conditioned with *IN91. When this indicator is set to true and the Record Format is written to, then
    • The referenced message is pulled from the message file and displayed in the message line.
    • The field will be automatically set to DSPATR(RI) (Reverse Image) to show the user where the error occurred.
    • The given *IN91 (third parameter) will be reset after the Record Format is read, to automatically clear the error condition when the user submits the screen for processing.
  • ERRSTR is a (P)rogram defined field. This equals to a global variable in ILE RPG though the usual file reference. You may write a program provided string into this variable which is then shown at the corresponding position (&1) in the message file text. (See above.)

Subfiles

Handling Subfiles is always a lot more work than just coping with a simple Details Record Format. Error output is no exception in the best tradition of Subfiles.

In contrast with CHECKMSGID above, programmatic message output is done in the SFLCTL Record Format. This yields to an exception to the rule stated above: Here, the output is not tied to a field. Example excerpt:

     A                                      ERRSFL
     A          R MYCTL                     SFLCTL(MYSFL)
     A                                      […]
     A  91                                  SFLMSGID(ERR1218 GENERICMSG 91 &ERRSTR)
     A            ERRSTR        16A  P

Again, noteworthy stuff:

  • The SFLMSG(ID) statement is specified in the global section of the SFLCTL, before any fields are defined, and conditioned with *IN91. When this indicator is set to true and the Record Format is written to, then
    • The referenced message is pulled from the message file and displayed in the message line.
    • The given *IN91 (third parameter) will be reset after the Record Format is read, to automatically clear the error condition when the user submits the screen for processing.
  • ERRSTR is a (P)rogram defined field. This equals to a global variable in ILE RPG though the usual file reference. You may write a program provided string into this variable which is then shown at the corresponding position (&1) in the message file text. (See above.)

Per default, there is no visible indication where the error happened. This tying to a specific field must be done by the programmer. Fortunately, this is really easy:

     A                                      ERRSFL
     A          R MYSFL                     SFL
     A            OPT            1A  I  9  3VALUES(' ' '2' '4' '5' '+' '-')
     A                                      CHKMSGID(SFL0001 GENERICMSG)
     A  91                             DSPATR(RI)

The OPT-Field definition is the same as stated above. Additionally, the last line sets OPT to DSPATR(RI) (Reverse Image) when *IN91 is set on to indicate an error, which is utilized in the SFLCTL anyway. So we can show the user where the error occurred in the record (aka: which field).
Since a Subfile is a way to show many likewise records on one screen, every entry of a subfile has it's own set or unset indicators. Since *IN91 is set on only for that particular row, the user can see where the error occurred. In which record (aka: which line), to be precise.

Subfile Next Changed

An important extension to SFLMSG(ID) is SFLNXTCHG. Imagine the following:

  • An user fills out an SFL form and sends the data by pressing Enter.
  • Your SFL Routine wades through all changed SFL records, detects an error somewhere, sets *IN91 as stated above and writes back to the screen.
  • Your user doesn't change anything and just again presses Enter.
  • Your SFL Routine again wades through all changed SFL records, skips the erroneous record because the user has not changed it.

In the end, the record on disk will stay the same as the user didn't get a second error message and thinks all went well. This undiscovered loss of data usually is not acceptable. Here SFLNXTCHG comes into play. If active, the complete record is marked as changed.[5] Example excerpt:

     A                                      ERRSFL
     A          R MYSFL                     SFL
     A  91                             SFLNTXCHG
     A            OPT            1A  I  9  3VALUES(' ' '2' '4' '5' '+' '-')
     A                                      CHKMSGID(SFL0001 GENERICMSG)
     A  91                                  DSPATR(RI)

The OPT-Field definition is the same as stated above. The new third line introduces SFLNXTCHG. If an error condition for a SFL record (aka: line) is signaled by *IN91, SFLNXTCHG is activated for the whole MYSFL record format (aka: all fields thereof). So the record is marked as changed and will be read when your program is instructed to iterate through the subfile again.

Implementation in ILE RPG

While there are many things to consider and change beforehand, implementing your message output in ILE RPG is mainly a question of setting an Indicator in the right section of your code flow, followed at least by a WRITE to the display file. See Catching file errors in ILE RPG for details.

Putting it all together

By now it's probably most easily seen that because necessary changes have to be made in three distinct files/places, it's important to plan your output.

  1. Use your program. Try to break it.[6] If you're kind of frightened to do so, make a backup of the library, so nothing will be lost. If still frightened what gigantic pile of errors will stack up for you to fix, let someone else break it. This circumvents your probable hesitation.
  2. Take notes when you would expect something else than standard OS/400 error messages. Note how you can reproduce these conditions.
  3. Create Message Descriptions with appropriate Message-IDs. See example ID above: You can take the file error number as a hint to your own message. 1218 = Record already locked. Also take care of the SEV-Field. When you compile source to objects, everything above 29 is fatal by default, everything else is considered just a warning and pure informational messages get a SEV(0). That's the measure I'd take for my messages, so a user can filter out stuff with CHGMSGQ.
  4. Make a table somewhere, mapping Error Codes to Indicators. Note that likewise errors encountered in your program, at different screens stem from the same condition and as such can be applied to the same Indicator. You have only roughly 60..70 available after implementing Function Keys (CA/CF, Scrolling) and SFL specific indicators.
  5. Insert error detecting routines in your application code. Set indicators as planned in the previous step.
  6. Implement recovery procedures for errors as necessary. Sometimes, they're implicit by halting the application at the next DSPF READ with a set error indicator. A good example for handling a locked record would be to re-read it and display it in (fake) output only fields while a message is stating that the record is locked and has been opened read-only.

Footnotes

  1. I deliberately don't state one line, since one record in a subfile display may actually span multiple lines in a display file. This is not limited to an implicit wrap around at the right end of the screen.
  2. Since they usually contain more fields to display than a subfile.
  3. With one exception.
  4. Now you probably understand why the ERRMSG(ID) is always tied to a field.
  5. For that reason, SFLNXTCHG is always switched on as needed by your program via an Indicator
  6. For example by starting a second instance, lock a record by READing it and leave it there. Now try to READ the same record in your first instance.

See also

Weblinks