Dynamic ISPF Panels
Typically, ISPF panels are very static in nature, at least as far as the content is concerned. Input and output fields are ‘named’ and can be set and contents retrieved by the underlying application through the use of variables and variable services. In addition the panel can contain a wide variety of processing statements to validate the input, thus relieving the application of much of that work.
In all my years working with mainframes I have never worked or had the need to use any other type of ISPF panel – until now!
I was looking at adding an ISPF based ‘wizard’ to some code and wanted to make is a ‘consumable’ as possible.
Well, the reality is that I could have used the typical ISPF static panel but I decided I wanted to try to mimic the more modern dynamic web page applications where the content of the page changes depending upon the options you select.
What I found was that you can define a ‘dynamic’ area in an ISPF panel and actually build the contents of the screen in the application. While I found some general guidelines on how to do this I did not find any specific examples of what you might call ‘real world’ code and so, as usual, I had to invent it all myself!
This article is the result of my experimentation. There are some things still missing like cursor positions for one and handling input and output fields that change order (you’ll see why this is important later on) but should you feel the need to have a go with creating dynamic ISPF panels, hopefully this will help get you started. They are pretty cool!
By the way, since this was simply an exercise my example code is all done in REXX to keep things simple and easy to change.
Panels and execs are all in the the standard ISPF locations. nothing fancy there.
The Panel Definition
Even though you can build pretty much the entire panel in the application, you still need a basic panel definition in the ISPPLIB concatenation. This is mine:
The dynamic area is defined by the ‘$’ symbol in the panel. It extends the full width of the panel (80 chars) and is referred to by the name DYNVAR. The ‘$’ are NOT treated as attribute characters and do NOT take up space on the panel, they are in fact part of the dynamic area.
I also defined several hexadecimal attribute character strings x’01’ through x’05’. I used hexadecimal characters so that there is no way the user can enter them into the input areas. This is important because YOU have to find them in the screen var (DYNVAR in my case) in your code and if you sued a character the user could enter you might mistake user input for attribute characters.
Cursor positions is currently rudimentary and fixed. I position the cursor at column 27 in the dynamic screen variable DYNVAR, which is the location of my first input field. Ifthe location of the first input field could move I would have to make that positioning more dynamic by using a variable and setting the offset in the dynamic var in the application code. For now, this works though!
The Dynamic area
In my case, the dynamic area is 80 characters long. The way it is built is that the first 80 characters of whatever is in the DYNVAR variable are used to build the first line of the dynamic area. Then the next 80 characters are used for the second line, the next 80 characters for the third line and so on.
So, if I do the following in the application exec (My test panel is called Z991):
dynvar='This is a test.' address ISPEXEC 'DISPLAY PANEL(Z991)'
I would get this:
Notice the cursor is where I positioned it in the panel definition in spite of the fact there is nothing there yet!
In order to get something to display on the next line I have to pad that text out to 80 characters and then add the next line of text. So I’d need driving code something like this:
dynvar=fit2line('This is a test.')||'This s line 2.' address ISPEXEC 'DISPLAY PANEL(Z991)'
Which gives me this:
In case you are wondering what ‘fit2line’ is, it’s a little function I wrote that just pads out whatever is passed to it to a multiple of the line length (80 characters in my case). It looks like this:
Fit2Line: procedure parse arg in ll=80 /* must = line length */ l=length(in) // ll out=in||copies(' ',ll-l) return out
If I wanted to add a third line, the dynvar assignment would be something like this:
dynvar=fit2line('This is a test.')||fit2line('This s line 2.')||'This is line 3.'
So now lets add an input field at the cursor location.
Since DYNVAR starts in column 1 (remember the ‘$’ is NOT an attribute, just a marker for the dynamic area) and the cursor is at column 27 on line 1 of the area, we need to make the data up to the cursor position 26 characters long.
However, Since the cursor is at position 27 and this is going to be an input field, we need an attribute character just before it. It would also be nice if we had an attribute character before the text as well.
If you go back to the panel definition, you will see that it has the following attributes defined:
01 TYPE(DATAOUT) INTENS(LOW) 02 TYPE(DATAOUT) INTENS(HIGH) 03 TYPE(DATAIN) INTENS(LOW) COLOR(GREEN) PAD(_) 04 TYPE(DATAOUT) INTENS(LOW) COLOR(RED) 05 TYPE(DATAOUT) INTENS(LOW) COLOR(YELLOW)
Each 2 digit value represents a single hexadecimal attribute byte.
The input attribute is hex ’03’ and there are a variety of output attributes. I’m going to make the text yellow so I am going to use the hex ’05’ value.
My code now looks like this:
din='03'x doy='05'x dynvar=doy||SetSize('Enter something..............',24)||din
I defined the attributes as variables to make them easier to recognize.
SetSize is a simple routine to set that length of a string to a specified size. It looks like this:
setsize: Procedure parse arg in , l if length(in) < l then return left(in,l) return left(in||copies(' ',l),l)
This is what the resultant panel looks like:
Slight problem! We did not set the location of an attribute to indicate the end of the input field/start of the next field. Hence it was continued all the way to the end of the dynamic area!
Remember that everything follows on sequentially so we need to set a string to the required length of the input field and then add in a closing attribute byte. The input field string can be spaces or it can contain some initial data. So now my code looks like this:
dol='01'x din='03'x doy='05'x dynvar=doy||SetSize('Enter something..............',24)||din||, SetSize('here',8)||dol
I’ve added a ‘dataout low’ attribute (see panel definition) and set the input field size to 8 characters. I’ve also initialized the input field data to the word ‘here’. Now my screen looks like this:
If I wanted to add another input field on the third line so that there is a blank line between the input lines then my code becomes something like this:
dol='01'x din='03'x doy='05'x dynvar=doy||SetSize('Enter something..............',24)||din||, SetSize('here',8)||dol dynvar=fit2line(dynvar)||fit2line(' ')||, doy||SetSize('Enter something else.........',24)||din||, SetSize('here',8)||dol
Notice that after setting the first line up I had to use the fit2line function to expand it to fill the whole line. Then I used the fit2line function with a single space to cause it to add a blank line and finally I added the new line to the dynamic variable. My screen now looks like this:
Of course the data I have displayed in the input fields could have come from variables instead of hard coded literals, as could the prompts.
In the next part of this article I shall look at the steps needed to extract the input data from the screen into the application.