This method for deploying menus uses only three objects to represent as many menus as needed. And menus can be changed by editing the file's records, instead of the code.
A menu is a list of options from which the user makes a selection. Each option is a brief description of the job that will be run when the user makes that selection. An option can lead to a program or to another menu of options. In IBM i, a menu consists of one display file, one message file, and one *MENU object [1]. In the IBM i environment, the display file contains the menu image, the message file contains the commands, and the *MENU object contains the name of the display file and the message file. In order to create one menu, we have to create a source file for the specified menu, define prompts for the menu image, define the command source for the menu, and finally save and compile the menu and command sources. If we want to change the menu, we have to reedit the code and recompile objects. This method is inconvenient if there's a large amount of menus, if we need to change options, or even if we want to avoid editing code.
Here, we present an alternative method for deploying menus in IBM i. All menus in our system can be stored in one DDS file and displayed by an array-subfile program. Each menu is stored as a tree-like form in the DDS file. Thus, the Dynamic Menu program can assign a menu with its options and can be easily navigated.
Graph and Tree Data Structure Theory
The Tree Data Structure (T-DS) is one of the most powerful data structures; it's often used in advanced tasks such as AI and compiler design [2, 3, 4]. Rooted trees can be used to store data in the computer's memory in many different ways. Unlike array, linked list, stack, and queue, which are linear data structures, tree is a hierarchical data structure. But what exactly is a tree? A graph is considered a set of vertices/nodes, which represent objects, data, or even operations, and edges/links, which represent links between the nodes.
A tree is a special kind of graph that follows a particular set of rules and definitions (Figure 1):
- Connected—A graph can be a tree if it is connected. Each node is connected with a link to at least one other node.
- Acyclic—A graph can be a tree if is acyclic. That means there's only one route from any node to any other node.
- Root: The term root commonly refers to a top-most node. In Figure 1, node F is the root of the tree.
- Descendant—A descendant is a node that is farther away from the root than some other node. The term descendant is always in reference to another node. In Figure 1, nodes I and H are descendants of node G.
- Parent—Parent is considered the node that is closer to the root node by one link or vertex. In Figure 1, node B is the parent of nodes A and D.
- Sibling—Sibling nodes share the same parent. In the example, nodes A and D are siblings.
- Ancestor—An ancestor is any node between a given node and the root, including the root. In the example, the ancestors of node H are nodes I, G, and F.
- Leaf or Terminal Node—A node is terminal if it has no children. In the example, node C is a leaf.
- Height—The height of a tree is defined as the number of vertices traversed to get to the most distant node. In Figure 1, the height of the tree is equal to three.
Figure 1: This is an example of a tree data structure.
Dynamic Menu
In menus, information naturally forms a hierarchy; objects are ordered "above" or "below" other objects. Tree data structure is a very efficient way to represent this type of information. In the problem at hand, each menu is a rooted tree. Root can represent the base menu while descendants of the nearest lower level can represent the options of the base menu. In this TechTip, nodes represent menu's options and new sub-menus, while leafs represent programs.
To store information in a hierarchical manner, a DDS file with the following fields will be used. Each record in this file represents one vertex (node or leaf) of the tree.
- DYNFUNCT (not unique key)—Since Dynamic Menu is designed to hold all of our system's root menus, we need a field to classify the records and assign them to a specific root menu. This segregation can be done based upon this field.
- DYNSTYPE—This field holds the record's type. A record can be either node type (new sub-menu) or leaf type (program to be called or command).
- DYNSCODE—This is the record's code ID. If this record is a leaf type record, it holds the name of the program that will be called. If this record is a node record, it holds the name of the new sub-menu.
- DYNCFROM—This field displays the node (menu or sub-menu) in which this record belongs. In other words, this field holds the code ID of this record's parent node.
- DYNLEVEL—Each record has a level. For example, root record belongs to the first level.
- DYNSDESC— This field is the option's brief description. If the record is a root menu or sub-menu, this description is shown as screen header.
- DYNSSORT—This is for the menu options order. For example, the record with sort value equal to 2 will be displayed higher than its sibling record with sort value equal to 5.
Listing 1 shows an example of the DDS file code. Of course, the file and the logic can be expanded to deal with authority issues or to call leaf programs with additional input parameters, commands, etc. See the example data displayed in Listing 2.
A R DYN0001 TEXT('Menu Tree file')
A DYNSCODE 10A TEXT('Screen Code (NAME)')
A DYNCFROM 10A TEXT('Called From')
A DYNLEVEL 1S 0 TEXT('Screen Level')
A DYNSTYPE 1A TEXT('Screen Type')
A DYNSDESC 50A TEXT('Screen Description')
A DYNPARAM 1S 0 TEXT('Program Parameters')
A DYNSSORT 3S 0 TEXT('Program Sort List')
A DYNFUNCT 10A TEXT('Calling Function')
Listing 1: DDS file code
DYNSCODE DYNCFROM DYNLEVEL DYNSTYPE DYNSDESC DYNPARAM DYNSSORT DYNFUNCT APSMN01 1 N Root menu 0 1 950030
PGMNAME1 APSMN01 2 L Leaf program 1 0 5 950030
APSMN02 APSMN01 2 N Sub-Menu 1 0 6 950030
PGMNAME2 APSMN01 2 L Leaf program 2 0 1 950030
PGMNAME3 APSMN01 2 L Leaf program 3 0 2 950030
APSMN03 APSMN01 2 N Sub-Menu 2 0 4 950030
APSMN05 APSMN03 3 N Sub-Menu 2.1 0 1 950030
PGMNAME4 APSMN03 3 L Leaf program 4 0 7 950030
PGMNAME5 APSMN05 4 L Leaf program 5 0 1 950030
PGMNAME6 APSMN05 4 L Leaf program 6 0 2 950030
PGMNAME7 APSMN05 4 L Leaf program 7 0 3 950030
PGMNAME8 APSMN05 4 L Leaf program 8 0 4 950030
Listing 2: Example of file's data
The program that manipulates the file's data is in essence an array-subfile program [5]. The dynamic menu (DMenu) program uses one main array as a pool and one screen array to fill the screen subfile. Initially, all records related to the root menu are loaded in a main array. Each time, the dynamic menu program reads the appropriate records from the main array and creates a subfile of output records via the screen array. When the entire subfile is written, the program sends the entire subfile to the display device [6]. The flow diagram is displayed in Figure 2. Please note that this TechTip is not intended to explain how an array-subfile program works.
Figure 2: Dynamic menu flow diagram
As mentioned before, the dynamic menu can be used to display all menus in a system. Thus, DMenu should be called with at least one input parameter (of course, entry parameters can be expanded to return errors or messages to the previous program, etc.) in order to select only the records that are needed for the specified root menu.
Two basic arrays are used. Initially, all records related with the root menu are loaded in the main array. In addition, all option records related with a sub-menu (or root menu) are loaded to the screen array in order to be displayed on screen. The options from the screen array are loaded to the screen subfile. The screen heading is loaded from the description of the submenu's (level one) root record. An example of the arrays definition is shown in Listing 3.
* Work Array for Main File
D I S 5I 0 Inz(*Zeros)
D SavI S 5I 0 Inz(*Zeros)
D WrkMenu DS QUALIFIED
D ArrAll 81A DIM(32676)
D MenCode Overlay(ArrAll)
D Like(DYNSCODE)
D MenFrom Overlay(ArrAll:*Next)
D Like(DYNCFROM)
D MenSort Overlay(ArrAll:*Next)
D Like(DYNSSORT)
D MenLeve Overlay(ArrAll:*Next)
D Like(DYNLEVEL)
D MenType Overlay(ArrAll:*Next)
D Like(DYNSTYPE)
D MenDesc Overlay(ArrAll:*Next)
D Like(DYNSDESC)
D MenI Overlay(ArrAll:*Next)
D Like(I)
D MenPara 1S 0 Overlay(ArrAll:*Next)
* Work Array for Screen fields
D SI S 5I 0 Inz(*Zeros)
D SavSI S 5I 0 Inz(*Zeros)
D WrkScrn DS QUALIFIED
D ArrSAll 68A DIM(32676)
D ScrSort Overlay(ArrSAll)
D Like(DYNSSORT)
D ScrSI Overlay(ArrSAll:*Next)
D Like(SI)
D ScrDesc Overlay(ArrSAll:*Next)
D Like(DYNSDESC)
D ScrCode Overlay(ArrSAll:*Next)
D Like
(DYNSCODE)
Listing 3: Main and Screen arrays definition in D-spec
When a user selects an option, the program will check the field that shows the type (DYNTYPE) of the corresponding record to see if the option is a node (value 'N') or a leaf (value 'L'). When the option is a leaf, DMenu calls the program whose name appears in the code ID field (DYNSCODE) of the option's corresponding record. On the other hand, if the option is a node, screen array is reloaded with root record equal to the corresponding record of the user's option.
Let's look at the following example, which is based on the data of Listing 2. Call the dynamic menu with the appropriate input value (Listing 4):
CALL DYNMENU PARM('950030')
Listing 4: Calling the dynamic menu
All records from the DDS file are copied to the main array. This is happening because the DYNFUNCT field value is equal to 950030 in all records. Thus, all records in the DDS file belong to only one root menu. The root record's description is displayed in the screen's header, while the records that are assigned to the root menu (e.g., rrn: 2 to 6, DYNCFROM = "APSMN01") are passed to the screen array. The screen array is sorted by the DYNSSORT field. The screen subfile is filled by the screen array (Figure 3).
Figure 3: Root menu
By selecting option 3, the procedure is repeated. The description of the Sub-Menu 2 is displayed in the screen's header, while the records that are assigned to this sub-menu (rrn: 7, 8) are passed to the screen array. The screen array is sorted by the DYNSSORT field, and it fills the screen's subfile (Figure 4).
Figure 4: Sub-Menu 2
When the user presses F12-Cancel, the program searches the main array for the corresponding record of the Sub-Menu 2 node. That is, it searches the record with a DYNSCODE value equal to APSMN03, which is the code ID of the Sub-Menu 2 node. The new menu is defined by the DYNCFROM value. The root menu screen is diplayed again (Figure 3).
Dynamic Menu Code
Of course, there are many ways to implement the code of a dynamic menu. The following snippets of code constitute a simple DMenu creation example.
As mentioned before, initially the main array must be loaded with all the root menu's related records. Listing 5 shows a code snippet for loading the main array from the file, where variable InpEntry is the program’s input parameter (e.g., 950030) and DYN00L4 is a logical file with one key (DYNFUNCT). The code ID of the root menu record is stored in the FirstScreen global variable.
P LoadMainA B
D PI
C Clear I
C Clear SavI
C Clear WrkMenu
C Clear FirstScreen
C InpEntry SetLL DYN000L4
C DoU %EoF(DYN000L4) Or InpEntry <> DYNFUNCT
C Read DYN000L4
C If Not %EoF(DYN000L4) And
C InpEntry = DYNFUNCT
C If DYNLEVEL = 1
C Eval FirstScreen = DYNSCODE
C EndIf
C Eval I = I + 1
C Eval WrkMenu.MenCode(I) = DYNSCODE
C Eval WrkMenu.MenFrom(I) = DYNCFROM
C Eval WrkMenu.MenSort(I) = DYNSSORT
C Eval WrkMenu.MenLeve(I) = DYNLEVEL
C Eval WrkMenu.MenType(I) = DYNSTYPE
C Eval WrkMenu.MenDesc(I) = DYNSDESC
C Eval WrkMenu.MenPara(I) = DYNPARAM
C Eval WrkMenu.MenI(I) = I
C EndIf
C EndDo
C Eval SavI = I
P
E
Listing 5: Load Main array example.
The screen array's loading procedure can be called with the menu's code ID as an input parameter (e.g., FirstScreen variable). This procedure is divided in two sections (Listing 6). The first section is responsible for filling the screen array if the input node is a root node, while the second section is responsible for filling the screen array in every other case. At the end, the screen array is sorted by the DYNSSORT file's sort field. In addition, a fold/unfold indicator is used to leave a blank between the menu's options. If the menu doesn't have many options (e.g., '7' in this example), then this indicator goes *On and the subfile displays the options with a blank record between them (Figure 3).
P LoadArray B
D PI Like(SI)
D InpFrom Like(DYNSCODE) Const
* Local Variables
D WrkLevel S Like(DYNLEVEL)
D Inz(*Zeros)
D TemLevel S Like(DYNLEVEL)
D Inz(*Zeros)
D WrkName S Like(DYNSCODE)
D Inz(*Blanks)
D TemName S Like(DYNSCODE)
D Inz(*Blanks)
D WrkI S Like(I)
D WrkSI S Like(SI)
C Clear SI
C Clear SavSI
C Clear WrkSI
C Clear WrkScrn
C Clear WrkI
C Clear WrkName
C Clear TemName
C Clear FoldR
* Check if Init
C Select
C When InpFrom = *Blanks
* Sort By Level (Care only for Level 1)
C SortA %SUBARR(WrkMenu.MenLeve:1:SavI)
C Eval WrkLevel = 1
C DoU WrkLevel <> TemLevel
C Eval WrkI = WrkI + 1
C Eval TemLevel = WrkMenu.MenLeve(WrkI)
C If TemLevel = WrkLevel
C Eval SI = SI + 1
C Eval WrkScrn.ScrSI(SI) = SI
C Eval WrkScrn.ScrDesc(SI) = WrkMenu.MenDesc(WrkI)
C EndIf
C EndDo
C Eval SavSI = SI
C Other
C If SavI > 0
* Sort By Name (Code)
C SortA %SUBARR(WrkMenu.MenFrom:1:SavI)
* Find Name (Code) Line
C Eval WrkI = %LookUp(InpFrom:WrkMenu.MenFrom:1:
C SavI)
C If WrkI > 0
C Eval WrkName = InpFrom
C DoU WrkName <> TemName
C Eval TemName = WrkMenu.MenFrom(WrkI)
C If TemName = WrkName
C Eval SI = SI + 1
C Eval WrkScrn.ScrDesc(SI) = WrkMenu.MenDesc(WrkI)
C Eval WrkScrn.ScrCode(SI) = WrkMenu.MenCode(WrkI)
C Eval WrkScrn.ScrSort(SI) = WrkMenu.MenSort(WrkI)
C EndIf
C Eval WrkI = WrkI + 1
C EndDo
C Eval SavSI = SI
C EndIf
C EndIf
C EndSl
C If SavSI > 0
C SortA %SUBARR(WrkScrn.ScrSort:1:SavSI)
C For WrkI = 1 to SavSI
C Eval WrkScrn.ScrSI(WrkI) = WrkI
C EndFor
C If SavSI > 7
C Eval FoldR = *Off
C Else
C Eval FoldR = *On
C EndIf
C EndIf
C Eval SavCurrPGM = InpFrom
C Return SavSI
P
E
Listing 6: Load Screen array
It must be noted that the input record's code ID is stored in the SavCurrPGM global variable.
Another important procedure is the Get Previous Sub-Menu procedure (Listing 7). It takes as input the SavCurrPGM global variable and updates the SavPrevPGM global variable. Then the SavPrevPGM variable can be used as an input in the Load Screen Array procedure to load the previous menu.
P GetPr B
D PI Like(DYNSCODE)
D InpCode Like(DYNSCODE) Const
* Local Variables
D WrkI S Like(I) Inz(*Zeros)
C Eval WrkI = 0
C Eval WrkI = %LookUp(InpCode:WrkMenu.MenCode:1:
C SavI)
C If WrkI > 0
C Return WrkMenu.MenFrom(WrkI)
C Else
C Return *Blanks
C EndIf
P E
Listing 7: Get previous sub-menu
The Call Leaf Program procedure takes as an input the code ID of a leaf record and calls the program that the leaf record indicates.
P CallingPGM B
D PI
D InpCode Like(DYNSCODE) Const
* Local Variables
D WrkI S Like(I) Inz(*Zeros)
D WrkError S 7A Inz(*Blanks)
* General Calling Procedure for Programs without I-O Variables
D PgmName S 10A Inz(*Blanks)
D CallPGM PR EXTPGM(PgmName)
C Eval WrkI = 0
C Eval WrkI = %LookUp(InpCode:WrkMenu.MenCode:1:
C SavI)
C If WrkI > 0
C If WrkMenu.MenPara(WrkI) = 0
C Eval PgmName = WrkMenu.MenCode(WrkI)
C CallP CallPGM()
C Else
*If option's leaf program has more than zero inputs we can call it here
C EndIf
C EndIf
P
E
Listing 8: Call leaf program
Finally, the following procedure takes as an input the code ID of a record and returns *On if the record is a leaf record.
P IsTypeL B
D PI Like(WrkInd)
D InpCode Like(DYNSCODE) Const
* Local Variables
D WrkI S Like(I) Inz(*Zeros)
C Eval WrkI = 0
C Eval WrkI = %LookUp(InpCode:WrkMenu.MenCode:1:
C SavI)
C If WrkI > 0
C If WrkMenu.MenType(WrkI) = 'L'
C Return *On
C Else
C Return *Off
C EndIf
C EndIf
P E
Listing 9: Check if a record is a type leaf record
Summary
This TechTip presents an alternative method for deploying menus in IBM i. In IBM i, each menu is composed of three different types of objects (*MSGF, *FILE, *MENU). Each time we need to maintain a menu, we have to edit the code and recompile the objects. Instead of creating different objects with different source types for each different menu, by employing the proposed method we need only three objects (one screen, one program, one file) to represent as many menus as we like. All the menus are stored in one DDS file, and we can edit them without an intervention in the code. Finally, this method prevents the repeated programs' call, resulting in better performance and in avoiding the "many calls" limit.
References
[1] John Enck, (1997), Navigating the AS/400: A Hands-On Guide, Prentice Hall PTR, ISBN 10: 0138625581
[2] Gersting,J.L., (2007), Mathematical Structures for Computer Science, W.H. Freeman and Company.
[3] Diestel, Reinhard, (2005), Graph Theory (3rd ed.), Berlin, New York: Springer-Verlag, ISBN 978-3-540-26183-4.
[4] Steven S. Skiena, (2008), The Algorithm Design Manual (2nd Edition), Springer, ISBN: 978-1848000698
[5] Kevin Vandever, (2011), Subfiles in Free-Format RPG, MC Press, ISBN-10: 1583470948
[6] IBM, (2006), WebSphere Development Studio Client for iSeries—Using Subfiles, IBM Corporation 1992, 2006
LATEST COMMENTS
MC Press Online