In the past two issues of this newsletter, I've explained the details of CGI programming in RPG. In both articles, I've said that this information might be good to know, but in a real application you should use a toolkit. In this issue, I'll demonstrate how to use one such toolkit, IBM's CGIDEV2.
Don't worry if you haven't read the previous two installments of this series; they're not necessary to use CGIDEV2.
CGIDEV2 is an attractive choice for RPG developers because it simplifies the task of writing Web applications and it's free.
In order to get started with CGIDEV2, you'll need to download it and install it on your system. You can download it from IBM at the following link:
http://www-922.ibm.com
Once downloaded, you'll have a ZIP file that contains two things: a save file containing the actual CGIDEV2 software and a README.TXT file that explains how to install the save file.
From this step on, I'll assume that you've downloaded and installed CGIDEV2 by following the instructions that came in the ZIP file.
You'll need a library that's designated as a "CGI" library. The HTTP server needs to know that when you reference an object in that library, it's a program that should be run instead of a document that is to be downloaded. The HTTP server will therefore pass the input from the browser to your programs and wait for your programs to send back an HTTP response.
CGIDEV2 is kind enough to set up that configuration for you. It'll also make a copy of some of the tools that you need to write Web pages into the library that you designate. To set all of this up, run the SETCGILIB command from CGIDEV2.
For example, the following commands create a library called CLUBTECH, install the needed objects from CGIDEV2, and configure my Apache HTTP server so that I'm ready to go:
CRTLIB LIB(CLUBTECH) CGIDEV2/SETCGILIB SRCLIB(CLUBTECH)
Once that's finished, I'll have to start (or re-start) the Web server instance to activate the new configuration.
The following are the configuration keywords that CGIDEV2 makes to the Apache HTTP server:
#---- CLUBTECH directives AliasMatch /clubtechh/(.*)\.htm /QSYS.LIB/CLUBTECH.LIB/HTMLSRC.FILE/$1.mbr Alias /clubtechh/ /QSYS.LIB/CLUBTECH.LIB/HTMLSRC.FILE/ Alias /clubtech/ /clubtech/ ScriptAliasMatch /clubtechp(.*).pgm /qsys.lib/clubtech.lib/$1.pgm <Directory /QSYS.LIB/CLUBTECH.LIB> AllowOverride None Options None order allow,deny allow from all </Directory> <Directory /clubtech> AllowOverride None Options None order allow,deny allow from all </Directory>
These directives provide 3 different pseudo-directories. Each one is redirected to a different place on the system. The /clubtechh/ (note the extra H) pseudo-directory is redirected to a member of the CLUBTECH/HTMLSRC file. The /clubtech/ (without the extra H) directory is pointed to the /clubtech area of the IFS. The /clubtechp/ pseudo-directory is treated as a location for CGI scripts in the CLUBTECH library. Any URL that starts with this directory name and ends with .PGM is assumed to be a program that Apache should run rather than a document for download.
For example, if I referenced the following URL, Apache would send back the contents of the FOO member in the CLUBTECH/HTMLSRC source physical file:
http://www.example.com/clubtechh/foo.htm
If I reference the following URL, the program called BAR in library CLUBTECH will be run:
http://www.example.com/clubtechp/bar.pgm
And if I make reference to the following URL, it will actually read a file called /clubtech/whatsamajig.html from the IFS:
http://www.example.com/clubtech/whatsamajig.html
Now it's time to write code. (Finally!) To provide an example of how to use CGIDEV2, I'll give you a tour of a simple customer address maintenance program. The file that it will store the addresses in will be named CUSTMAS and will have the following layout:
A UNIQUE
A R CUSTMASF
A CUSTNO 4S 0
A NAME 30A
A ADDRESS 30A
A CITY 13A
A STATE 2A
A ZIPCODE 5S 0
A K CUSTNO
A static HTML document will be used to run the RPG program. This document will display a form where the user will key in the customer number. I've called this document "editcust.html", and I'll store it in the /clubtech/ directory of the IFS. The following is an excerpt from the editcust.html document:
.
.
<FORM action="/clubtechp/editcust.pgm" method="post">
.
.
<INPUT type="text" name="CUSTNO" size="4" maxlength="4">
.
.
<INPUT type="submit" name="OKButton" value=" OK ">
.
.
</FORM>
.
.
The HTML code above is what does the "real work" of telling it which program to run and what fields to send to that program. I'll include the complete HTML file in the code download that accompanies this article.
When the user keys in a customer number and clicks the OK button, the browser will send the info to a program called EDITCUST. Since the HTTP configuration maps /clubtechp/ to the CLUBTECH library, that EDITCUST program must be in the CLUBTECH library on my system.
When you ran SETCGILIB, it installed some copy books into the source library. The one that contains the H-spec needed to bind to the CGIDEV2 service program is called HSPECBND. You'll need to use the /COPY compiler directive to include that member at the top of each program that accesses CGIDEV2. Once you've done that, you'll also need to use /COPY to bring in the prototypes from CGIDEV2, which are stored in the PROTOTYPEB copybook, and the definition of the API error structure, which is stored in the USEC member. The following code snippet shows these definitions in my EDITCUST program:
H DFTACTGRP(*NO)
/copy hspecsbnd
FCUSTMAS UF E K DISK
/copy prototypeb
/copy usec
The F-spec shown above is an ordinary RPG F-spec that will be used to access the CUSTMAS file. Now that I've used /COPY to bring in all of the definitions from CGIDEV2, I can go ahead and use its functionality.
CGIDEV2 uses specially formatted text files called "templates" (or "externally-defined HTML files) to store the output that will be sent to the browser. This makes your life easier because you don't have to try to code the HTML in your RPG program. I'll tell you more about templates in a moment.
One of the first things your RPG program has to do in order to use CGIDEV2 is load the HTML templates into memory. There are a few other steps that I do at the start of every CGIDEV2 program as well. Consider the following code:
D savedQuery s 32767A varying
.
.
SetNoDebug(*OFF);
gethtmlIFSMult( '/clubtech_html_templates/editcust.tmpl' );
qusbprv = 0;
ZhbGetInput(savedQuery: qusec);
The SetNoDebug(*OFF) code tells CGIDEV2 that I want debugging enabled. The getHtmlIFSMult() tells CGIDEV2 where to find my templates. The ZhbGetInput() routine tells CGIDEV2 to read the data sent from the browser and parse it into variables. It saves the original QUERY_STRING into the savedQuery variable for me, in case I want it. (Though, so far, I haven't found a use for it.)
It's possible to specify more than one template in the getHtmlIFSMult() call. To do that, just separate the templates with spaces. For example:
gethtmlIFSMult( '/clubtech_html_templates/editcust.tmpl ' +
'/something_else/template.ext');
The templates are HTML files that have been divided into sections. Each section is a "chunk" of HTML code that can be written to the browser individually. I like to create a "prelude" section that contains all of the HTML code for the top of the page and an "end" section that contains all of the code for the bottom of the page. Then I have different sections for the data that belongs in the middle, depending on what happens.
By default, each section in the template is delimited with /$ followed by the section name. The following is an excerpt from a template that illustrates this:
/$prelude
Content-type: text/html
<html>
<head>
<title>/%title%/</title>
</head>
<body text="black" link="blue" vlink="#871f78" bgcolor="white">
/$end
</body>
</html>
/$error
<h1>/%message%/</h1>
This example contains 3 sections called "prelude", "end", and "error". Everything after the section delimiter and before the next section delimiter is HTML code that will be sent to the browser when the section is written.
It's also possible to put variables in the sections. This is useful when you want to write data from your program to the HTML document. Variables are, by default, delimited by putting the /% characters in front of them and the %/ characters after them. If you look at the code above, /%title%/ is an example of one of these HTML variables.
To supply a value for the TITLE variable and write the prelude section to the browser, I'd run the following RPG code:
updHtmlVar('title': 'Edit Customer Address');
wrtsection('prelude');
The updHtmlVar() API is used to replace the /%title%/ variable in the template with the string 'Edit Customer Address. The wrtsection() API is then used to write the entire prelude section to the browser.
There's a third parameter to updHtmlVar() as well. This parameter is used to tell CGIDEV2 to clear all of its HTML variables. It's a good idea to pass this parameter the first time you call updHtmlVar() in your program. That way, you won't have to worry about CGIDEV2 having old variable values from the last program that used it. For example, the following code would first wipe out all of the HTML variables in CGIDEV2, then create one called title and give it a value:
updHtmlVar('title': 'Edit Customer Address': '0');
wrtsection('prelude');
When you're done writing all of the sections that your program needs to write, you should pass the special value of *fini to the wrtsection() API. Then CGIDEV2 will know that you're done, and it'll return all of the data back to the HTTP server, which will send it to the browser.
You can specify *fini in the same call to wrtsection() as the section name simply by separating it with a space. For example:
wrtsection('end *fini');
One of the nice things about the HTML templates is that you can use an HTML design tool (such as WDSC) to design what you want the screens to look like ahead of time. Then you can save the results to the IFS and go in and insert the section delimiters and variables using the EDTF command from the iSeries or from a tool like Notepad from Windows.
Another nice thing about the template files is that it's easy to teach a non-RPG programmer how to create your screens for you! This works particularly well when your shop has separate Web designers and RPG programmers. That way, the RPG guy can concentrate on the business logic and the Web designer can concentrate on the appearance.
In addition to using templates to write data to the browser, CGIDEV2 provides routines for reading input from the browser. As I mentioned before, the ZhbGetInput() routine tells CGIDEV2 to parse the data that the browser sent to your program. When you're ready to use that data, you can call ZhbGetVar() to retrieve the data that it parsed.
Back to the sample program. Once the user has keyed a customer number and clicked "Ok", the browser will create a variable called "custno" and send it to the HTTP server. The server will call the EDITCUST program. EDITCUST does some initialization and writes out the prelude section, as shown above.
Then EDITCUST needs to get the customer number from the browser, chain to the CUSTMAS file, and write out a new Web page containing the address so the user can edit it. I put all of the code to do that into a subprocedure called DisplayValues. This is what it looks like:
P DisplayValues B
D DisplayValues PI
/free
monitor;
custno = %int(zhbGetVar('custno'));
on-error;
updHtmlVar('message': 'Invalid customer number!');
wrtsection('error');
return;
endmon;
chain(n) custno CUSTMAS;
if not %found();
updHtmlVar('message': 'Customer number not found!');
wrtsection('error');
return;
endif;
updHtmlVar('custno': %char(custno));
updHtmlVar('custname': name );
updHtmlVar('custaddr': address );
updHtmlVar('custst' : state );
updHtmlVar('custcity': city );
updHtmlVar('custzip' : %editc(ZipCode:'X'));
wrtsection('displayvals');
/end-free
P E
The ZhbGetVar() API from CGIDEV2 returns one of the variables submitted from the browser. It returns the variable in character format, so the program uses the %int() BIF to convert it to a number. (This requires V5R2. On an older release, you'd want to use atoi() instead.)
If an error occurs while converting the value to character, the EDITCUST program writes out the error section to tell the user that an error occurred. Otherwise, it loads the customer record and copies each field to an HTML variable.
Now all that remains is to call wrtsection() to write out the END section and *FINI and to end the program. The page will be written back to the browser and the user will be able to see the new Web page.
When the user has made his changes and he wants to update the file, the browser needs to run the program again. I have it set up in the <form> tag that's in the template to call EDITCUST again. This time, however, I've supplied a hidden variable in the HTML that tells it that I want to update the database rather than display the field values.
The following code is the mainline of the EDITCUST program:
SetNoDebug(*OFF);
gethtmlIFSMult( '/clubtech_html_templates/editcust.tmpl' );
qusbprv = 0;
ZhbGetInput(savedQuery: qusec);
//----------------------------------------------
// Output the top of the document
//----------------------------------------------
updHtmlVar('title': 'Edit Customer Address': '0');
wrtsection('prelude');
//----------------------------------------------
// Either update the database or display the
// values, depending on the contents of the 'action'
// field in the web form:
//----------------------------------------------
action = zhbGetVar('action');
if action = 'update';
UpdateFile();
else;
DisplayValues();
endif;
//----------------------------------------------
// Write the bottom of the results, and use
// *fini to send them to the browser.
//----------------------------------------------
wrtsection('end *fini');
return;
As you can see, it either calls the DisplayValues() routine to display the CUSTMAS values, or it calls the UpdateFile() routine to make updates to the database.
The UpdateFile() routine loads the variables from the browser and updates the CUSTMAS file. It then writes a "successful" message (another section in my template) to the browser. Here's the code:
P UpdateFile B
D UpdateFile PI
/free
monitor;
custno = %int(zhbGetVar('custno'));
on-error;
updHtmlVar('message': 'Invalid customer number!');
wrtsection('error');
return;
endmon;
chain custno CUSTMAS;
if not %found();
updHtmlVar('message': 'Customer number not found!');
wrtsection('error');
return;
endif;
name = zhbGetVar('custname');
address = zhbGetVar('custaddr');
state = zhbGetVar('custst' );
city = zhbGetVar('custcity');
monitor;
ZipCode = %int(zhbGetVar('custzip' ));
on-error;
updHtmlVar('message': 'Invalid zip code!');
wrtsection('error');
return;
endmon;
update CUSTMASF;
wrtsection('success');
/end-free
P E
You can download the source code for this article from the following link:
http://www.pentontech.com/IBMContent/Documents/article/51209_27_EditCust.zip
To learn more about CGIDEV2, be sure to check out the tutorial on IBM's site:
http://www-922.ibm.com