Although DATATRIEVE was designed to be and is best suited as an on-line, interactive, query and report writing language, its lack of built-in menu capabilities has not stopped a very large number of users from writing big menu-driven application in DATATRIEVE.
There are basically four ways to create menu-driven applications in DATATRIEVE.
These four ways of creating menus in DATATRIEVE have been described in presentations by Chris Wool at the last three DECUS Symposia entitled "Writing Menu-Drive Systems in VAX-DATATRIEVE." A summary of the important features of each method has appeared in the DTR/4GL Session Notes at each Symposia. In addition, magic presentation by Bob Hoover,1 Lorey Kimmel,2 Gary Burton,3 Pat Scopelliti,4 Larry Jasmann,5 and articles by Diane Pinney using logicals with a pseudo-recursive procedure,6 by Berrie Gray using a very innovative approach to circumventing the poor performance of "pre-compiled" procedures,7 and by S. Begelman using callable DATATRIEVE8 illustrate examples of these techniques.
It is not the purpose of this article to compare and contrast the features and capabilities of these four methods, but to describe an extension to one of these methods which, I believe, makes the technique far superior to all the others. The menu technique which I will fully describe below is of the type which uses logicals and a pseudo-recursive procedure, but it contains three important extensions to the basic technique. These three extensions are:
Consider the following domain and record definitions:
DEFINE DOMAIN MENU USING MENU_RECORD ON MENU$DATA:MENU.DAT; DEFINE RECORD MENU_RECORD USING OPTIMIZE 01 MENU_REC. 03 MENU_GROUP_KEY. ! group key is primary key 05 MENU_SYSTEM PIC XX. 05 RECORD_TYPE PIC 9. ! 0 - header, 1 - control 05 OPTION PIC 99. ! selection (and order) 03 PROC_NAME PIC X(31). ! procedure name or blank if header 03 RIGHTS_OWNER PIC X(10). ! tag of the owner of this menu function 03 DESCRIPTION PIC X(80). ! header information or option description ;
The fields in the domain MENU are described as follows:
MENU_SYSTEM: The values in the field MENU_SYSTEM are a two character abbreviation for the menu or sub-menu and are used to divide the menu system into easily manageable sub-menus. This field would have values such as "MN" for the main menu, "AR" for the accounts receivable sub-menu, "AP" for the account payable sub-menu, "SR" for the supervisor reports menu, etc. The information in this field (the value) is never seen by the user, but is used by the main menu procedure to determine which part or sub-part of the menu system is currently to be accessed.
RECORD_TYPE: There are two types of records in the MENU domain - header records and control records. RECORD_TYPE has the value "0" for header records and "1" for control records. Header records are used to create a menu header or title for each menu. Control records contain the control, security, and prompting information of each option on the menu.
OPTION: The field OPTION controls the order in which header records and control records are displayed on each menu. They are also the selection criterion for choosing an item from a menu.
MENU_GROUP_KEY: The group element, MENU_GROUP_KEY, is the primary key of the indexed domain MENU. By using this group element as a primary key, it is possible to avoid sorting the records in order to display them of each menu. This technique of using a group element as the primary key plays a very important role in giving this implementation good performance characteristics.
PROC_NAME: The field PROC_NAME is blank for header records. For control records it contains the name of the DATATRIEVE procedure which is to be executed when this particular menu item is selected.
RIGHTS_OWNER: The field RIGHTS_OWNER contains a string which is the name of the rights identifier which must be possessed in order to be allowed to make this menu selection. The importance of this field well be clarified when we described the tables and procedures below.
DESCRIPTION: The field DESCRIPTION contains the text which is displayed on the menu for both header and control records. Note that for header records this string field may contain escape sequences which activate terminal characteristics such as double height/double width characters.
The listing of two typical records in the MENU domain might look like:
MENU_SYSTEM : MN RECORD_TYPE : 0 OPTION : 01 PROC_NAME : RIGHTS_OWNER : ALL DESCRIPTION : <ESC>#3 Menu Data Management System MENU_SYSTEM : MN RECORD_TYPE : 1 OPTION : 01 PROC_NAME : MOVE_TO_SUPERS_MENU RIGHTS_OWNER : SUPERVISOR DESCRIPTION : Move to the Supervisor's Menu
The first record is a header record (RECORD_TYPE=0); it can be accessed by those who have the "ALL" rights identifier, and the DESCRIPTION field contains the first part (top part) of a two part entry which makes a double height, double width display of "Menu Data Management System". The second record is a control record (RECORD_TYPE=1); it is menu option 1 which activates the procedure MOVE_TO_SUPERS_MENU, and is described with "Move to the Supervisor's Menu". The MENU domain would contain one record for each menu option plus one or two header records for each menu and submenu.
In addition to the domain MENU, there are two domain tables which are used. These are as follows:
DEFINE TABLE MENU_SECURITY_TABLE FROM DOMAIN MENU USING MENU_GROUP_KEY : RIGHTS_OWNER END_TABLE DEFINE TABLE MENU_TABLE FROM DOMAIN MENU USING MENU_GROUP_KEY : PROC_NAME END_TABLE
Before we can start the menu system, certain global variables and logicals must be properly set up. The LOGIN.COM file of the account or the SYLOGIN.COM file needs to contain some DCL commands to establish the RIGHTS LIST for the user. This would be accomplished with some DEC that goes like:
$ . . . $ assign/user rightsxxx.dat sys$output $ show process/priv $ open/read file rightsxxx.dat $ rights :== "" $loop: $ read file r$ $ if (r$ .eqs. "Process rights identifiers:" then goto loop2 $ goto loop1 $loop2: $ read/end_of_file=eof file r$ $ rights = rights + r$ $ goto loop2 $eof: $ close file $ delete rightsxxx.dat;* $ assign/process "''rights'" processrights $ . . .
The net result of this DCL code is to create a process logical PROCESSRIGHTS which contains a list (separated by spaces) of the rights that the user's process possesses. The rights are granted to the user by the system manager with the GRANT?IDENTIFIER command with the AUTHORIZE utility. Determination of typical values for this logical might look like:
$ show logical processrights "PROCESSRIGHTS" = " INTERACTIVE LOCAL ALL DATAENTRY" (LMN$PROCESS_TABLE)
The DATATRIEVE startup command file which is used to automatically activate the menu system is as follows:
! dtrstartup.com DECLARE ESC PIC X(1). ! This is going to hold the ESC character. DECLARE CLEARIT PIC X(6) ! This going to hold the ESC sequence QUERY-HEADER IS -. ! to clear a video screen with no header. DECLARE BOTTOMIT PIC X(7) ! This is going to hold the ESC sequence QUERY-HEADER IS -. ! to position the curser on line 22. DECLARE RIGHTS_STRING PIC X(80). ! The rights list passed from DCL. If you have lots of rights, you may need ! more than 80 characters in the global variable. DECLARE MENU_TYPE PIC XX. ! This is the variable which controls which menu or submenu is used. It is ! initially set by this startup file, but is changed by various procedures ! to move about. DECLARE GROUP_SELECTION COMPUTED BY ! GROUP_SELECTION is used to MENU_TYPE|"1"|FORMAT SELECTION USING 999 ! lookup entries in the bales EDIT-STRING IS X(5). DECLARE SELECTION PIC 99 VALID IF GROUP_SELECTION IN MENU_TABLE AND RIGHTS_STRING CONTAINING GROUP_SELECTION VIA MENU_SECURITY_TABLE||" ". ! The complex validation clause on SELECTION assures us that menu selection ! entered is (first) a valid entry, and (second) an entry which this user ! is "privileged" (by holding rights list identifiers) to access the option. DECLARE LOOP_PROCEDURE PIC X(8). ! A string which contains the name of the procedure which is to be called ! pseudo-recursively. Ususally this will be MAINLOOP, but it will change ! when it is time to exit and break the recursive loop. ESC = "<ESC>" ! The escape character. Use GOLD 27 GOLD 3 in the EDT editor to set this. ! [Note added in 1997: it would be better to use the function fn$char(27).] CLEARIT = ESC|"[H"|ESC|"[J" ! The escape sequence to hame and clear a screen on VT100 and VT200 type terminals BOTTOMIT = ESC|"[22;1H" ! The escape sequence to position the curser at the beginning of line 22. :WORKING ! A procedure which is given below that put a blinking ". . . Working . . ." ! message on the screen to entertain the user while the rest of the ! startup command file is executed. MENU_TYPE = "MN101" via MENU_TABLE ! A dummy line which forces the MENU-TABLE to be loaded so that there will be ! no delay later. MENU_TYPE = "MN101" via MENU_SECURITY_TABLE ! A dummy line which forces the MENU-SECURITY-TABLe to be loaded so that there ! will be no delay later MENU_TYPE = "MN" ! Initially points to the MaiN menu RIGHTS_STRING = FN$TRANS_LOG("PROCESSRIGHTS") ! Move the rights list from the logical to a global variable. We will keep ! checking these rights and global will be faster than a logical. LOOP_PROCEDURE = "MAINLOOP" ! Set the name of the procedure MAINLOOP in global variable. FN$CREATE_LOG("MAINPROC",LOOP_PROCEDURE) ! The global variable is then moved to the logical READY MENU SHARED READ ! Shared ready is essential for a multi-user menu system. :MAINPROC ! Execute the main loop procedure. The logical MAINPROC is translated into ! MAINLOOP and then executed. EXIT ! This exit may or may not be executed deponding on which type of "exit" ! procedure is activated instead of MAINLOOP.
This DATATRIEVE startup command file may appear to be unnecessarily complicated. The complexity of having both the global variable, LOOP_PROCEDURE, and the logical, MAINPROC, is, in fact, necessary to allow the menu system to be "gracefully" stopped and an exit to either the DATATRIEVE prompt or DCL be made. This, I hope, will become clear when we discuss the several exit procedures.Now we have established enough of the environment that we can understand what is going on in the main pseudo-recursive procedure MAINLOOP. This procedure is as follows:
DEFINE PROCEDURE MAINLOOP PRINT CLEARIT ! Clear the screen PRINT DESCRIPTION(-) OF MENU WITH ! Print header lines for MENU_GROUP_KEY STARTING WITH MENU_TYPE|"0" AND ! this menu RIGHTS_STRING CONTAINING RIGHTS_OWNER||"" PRINT SKIP 2 ! Skip down a little bit PRINT COL 10, OPTION(-) using Z9, " - ", ! Print all menu option DESCRIPTION(-) USING T(55) OF MENU WITH ! lines for this menu MENU_GROUP_KEY STARTING WITH MENU_TYPE|"1" AND ! that the user is allowed RIGHTS_STRING CONTAINING RIGHTS_OWNER||"" ! to use. PRINT BOTTOMIT ! Put curser on line 22. SELECTION = *."selection from menu" ! Prompt user for selection. Remember the complex validation clause on ! selection assures us that the input is legal and that the user has the ! rights list privilege to choose the option FN$CREATE_LOG("CURRPROC",GROUP_SELECTION VIA MENU_TABLE) ! Translate the users choice (by number) into the name of the procedure ! that is to be executed. :CURRPROC ! Execute the procedure. ! ! Note that this procedure is not within a BEGIN-END loop. Therefore, any ! or all of the procedures so executed my contain DATATRIEVE command and ! statements such as READY, DEFINE, DEFINEP, DELETE, FIND, and SELECT ! which can not be used in large "pre-compiled" procedures with one large ! BEGIN-END loop. This has the advantage that one does not have to ready ! every possible domain that one might need. You can wait to ready a ! domain when (and only when and if) you need it. ! FN$DELETE_LOG("MAINPROC") ! See discussion below. FN$CREATE_LOG("MAINLOOP",LOOP_PROCEDURE) :MAINPROC ! See discussion below. END-PROCEDURE
Because the execution of MAINPROC (which is usually MAINLOOP procedure) is the last statement in the procedure MAINLOOP, the procedure is not actually calling itself recursively. However, since the procedure does, in fact, include a logical reference which is resolved to itself, procedures of this type a called pseudo-recursive (they appear to be recursive but really are not).
The last tree lines of the procedure (FN$DELETE_LOG, FN$CREATE_LOG, and execute MAINPROC) are at the heart of the pseudo-recursion. When the procedure MAINLOOP is invoked, the logical MAINPROC will be evaluated if it exits. Because the logical MAINPROC would not exist until after the FN$CREAT_LOG has been performed, it forces the compiler in DATATRIEVE toe-evaluate the logical MAINPROC each time it is used. By this ruse, it is possible to have one or more procedures which are called in the menu system which change the contents of the global variable LOOP_PROCEDURE and thus affect a "graceful" breaking of the infinite pseudo-recursive loop.
The procedure to terminate the menu system and cause a normal exit from DATATRIEVE is as follows:
DEFINE PROCEDURE STOP_MENU LOOP_PROCEDURE = "EXITPROC" END-PROCEDURE
All this procedure does is to change the contents of the global variable LOOP_PROCEDURE. When the MAINLOOP procedure deletes and creates the logical MAINPROC, the procedure will not not point to MAINLOOP but EXITPROC. EXITPROC is as follows:
DEFINE PROCEDURE EXITPROC PRINT CLEARIT END-PROCEDURE
Essentially all the procedure EXITPROC need do is clear the screen since by not recursively call MAINLOOP the loop is broken. Control then falls through to the last line of the startup file DTRSTARTUP.COM which is the DATATRIEVE command EXIT.
If one wants to break the loop and get to the DATATRIEVE prompt, one uses the procedure EXIT_PROCEDURE which is:
DEFINE PROCEDURE EXIT_PROCEDURE FN$DELETE_LOG("MAINPROC") SET ABORT ABORT END-PROCEDURE
Using the ABORT command will break all levels of the procedures and will deliver the user to the "DTR>" prompt of DATATRIEVE. Of course, one may not want a user in captured accounts to have access to this procedure.
Movement through the menu system is accomplished by very simple procedures which change the value of the global variable, MENU_TYPE. Two examples of such procedures are as follows:
DEFINE PROCEDURE MOVE_TO_SUPER_MENU ! A procedure to move to the supervisor's sub-menu MENU_TYPE = "SU" ! SU as the current menu END-PROCEDURE DEFINE PROCEDURE RETURN_TO_MAINMENU MENU_TYPE = "MN" ! set MN as the current menu again END-PROCEDURE
There are several "utility" type procedures which are very useful to have available in the menu system. The first of these, WORKING, was used in the DTRSTARTUP.COM file to pacify the user while the startup command file was executing. This procedure is:
DEFINE PROCEDURE WORKING DECLARE BLINK PIC X(4) QUERY-HEADER IS -. DECLARE NOBLINK PIC X(4) QUERY-HEADER IS -. BLANK = ESC|"[5m" ! The escape sequence for blink. NOBLINK = ESC|"[0m" ! The escape sequence for no attributes. PRINT CLEARIT, BOTTOMIT, " ",BLINK, ". . . Working . . .",NOBLINK END-PROCEDURE
Other useful procedure are:
DEFINE PROCEDURE NOT_YET PRINT "This procedure has not yet been implemented." FN$DCL("$WAIT 00:00:02") END-PROCEDURE DEFINE PROCEDURE SPAWN_MAIL FN$DCL("$MAIL") END-PROCEDURE DEFINE PROCEDURE SPAWN_PHONE FN$DCL("$PHONE") END-PROCEDURE
The procedure NOT_YET is used as a dummy procedure to hold a place in procedure development. If one is going to write a procedure whose name is FOO, but you haven't yet written it, then you can put FOO in the menu system along with its rights and description, but let FOO point to NOT_YET. This is kink of top-down implementation of procedures in this menu system. Note that NOT_YET contains a FN$DCL to the WAIT DCL command. If you are handy with re-linking DATATRIEVE, you may want to use a user define function like Don Stern's WL$WAIT9 instead of FN$DCL since it takes much less of the processor.
For menu system users who you wish to completely control, one can make the following command file as their login file.
$! $! caplogin.com $! $ on control_y then $goto done $ set control=(T,Y) $ assign [manager]dtrstartup.com dtr$startup $ assign/user tt: sys$input $ assign/user tt: sys$output $ dtr $done: $ lo/full
Their account qualifiers should be modified to be
disable control Y
default command interpreter
captive (can't change default disk)
use captive login.com file
allow one 1 subprocess
and the protection of the files [MANAGER]DTRSTARTUP.COM and [MANAGER]CAPLOGIN.COM should be only read and execute (not write or delete). With such an environment, there is pretty good change that most users can not escape for the menu system not can they do too much damage although it is still possible for them to use PHONE, MAIL, and the editor.
There is one more procedure which I find very helpful. I like the titles on my menus to be double height and double width. The following procedure is used to create the two header records for a new sub-menu. The procedure is HEADER_LOAD.
DEFINE PROCEDURE HEADER_LOAD ! ! A procedure to load header text for the menu systm. This procedure ! takesa text phrase, centers it, and loads it into two lines of menu ! header (in double height, double width) format. This procedure is ! not part of the operations of the menu system but is used as a ! utility to support and initialize new menus and submenus. ! READY MENU SHARED WRITE DECLARE BUFF PIC X(42). DECLARE UPPERLINE PIC X(3). DECLARE LOWERLINE PIC X(3). DECLARE T_MENU_SYSTEM PIC XX. DECLARE T_OPTION PIC 99. DECLARE T_RIGHTS_OWNER PIC X(10). DECLARE T_DESCRIPTION PIC X(80). DECLARE TWENTY_SPACES PIC X(20). DECLARE T_LENGTH USAGE IS INTEGER. ! TWENTY_SPACES = " " UPPERLINE = ESC|"#3" LOWERLINE = ESC|"#4" ! T_MENU_SYSTEM = *."Menu system abbreviation" T_RIGHTS_OWNER = *."Rights owner" BUFF = *."header line of text (40 characters or less)" T_LENGTH = (41 - FN$STR_LOC(BUFF," "))/2 STORE MENU USING BEGIN MENU_SYSTEM = T_MENU_SYSTEM RECORD_TYPE = 0 OPTION = 1 PROC_NAME = " " RIGHTS_OWNER = T_RIGHTS_OWNER DESCRIPTION = UPPERLINE|FN$STR_EXTRACT(TWENTY_SPACES,1,T_LENGTH)|BUFF END STORE MENU USING BEGIN MENU_SYSTEM = T_MENU_SYSTEM RECORD_TYPE = 0 OPTION = 2 PROC_NAME = " " RIGHTS_OWNER = T_RIGHTS_OWNER DESCRIPTION = LOWERLINE|FN$STR_EXTRACT(TWENTY_SPACES,1,T_LENGTH)|BUFF END END-PROCEDURE
What I have described is a complete. high-performance menu system. It appears very complex and difficult to initially set up. Bet the rewards of such a system pay off handsomely in maintainability of the system. Each procedure of the application is essentially independent of every other to the application. The greatest benefit of such a system occurs when the user needs a new function for the system. That function is written and tested. It is converted into a procedure. And then it is installed into the system by adding a sing record tot he MENU domain. This enhancement, of course, can be done while multiple users are simultaneously accessing the menu system!
Joe H. Gallagher, Ph. D.