Creating HTML tables with CGIDEV2

In this example we will build an HTML table using IBM's RPG language. Although the RPG language dates back from 1959 and IBM has been very good at updating the language periodically with new technology and techniques, they have somehow neglected to add a native vocabulary for generating HTML pages and communicating with a web server. Luckily, several third party products exist that perform these functions including the popular open source CGIDEV2 library.

In this example we will use CGIDEV2 to construct an HTML table. If we are a bit rusty in the concept of HTML tables, a brief review from w3schools can be found here.

We assume (optimistically) that at this point you have already downloaded and setup the CGIDEV2 package, and have an IBM Apache server (or equivalent) running on your IBM iSeries and a Web browser to test your program. If you do not have the above prerequisites, you may want to continue your reading the setup sections here.

This example will require two objects - a text html template in the iSeries IFS and a compiled RPG program using the CGIDEV2 service program.

Creating an HTML template in IFS

Because of the limitations of RPG source file members, CGIDEV2 recommends that we use the IFS to templates (or sleteton files) with fragements of HTML code used to construct our web page. More on these templates here. The extract below shows a template file that is used in a CGIDEV2 program. We will store this file in the iSeries IFS as /myIFSdir/salesReportIFS.html.

Content-type: text/html                              
   <H1>Sample Sales Report
   <table border =1 >                                
   /%Region%/ </td><td > /%Sales%/                   

At first glance this code may look similar to an regular HTML table. Actually is it a HTML template, sometimes called skeleton code. Let us take a look at some of the particularities of this template.

1. Note the use of named sections in this HTML template. In the above example, named sections are introduced with the <AS400> tag. The contents of a named section begins on the line following the named section. A named section is similar conceptually to a DDS display file record. The contents of a named section will only print to the web browser when the RPG program tells it to print with the WriteSection() procedure. In this case we are likely to print the Top, Header1, DataTop and DataBottom sections only once. The DataDetail section will be repeated with each successful iteration of the RPG Read OpCode.

2. Note the use of Substitution Variables. In the above example, we have two such substitution variables: /%Region%/ and /%Sales%/. These substitution variables will be replaced by values coming from the RPG program. CGIDEV2 procedure updHTMLvar() is used to push a variable from the RPG program to the HTML skeleton program

RPG Program Object created with CGIDEV2 Service Program

Here is the RPG code that drives the generation of our HTML web page.

 *----------------------------------------------------------- *
 * Program SLSRPT1                                            *
 * Sample RPG program used to write an HTML stream file       *
 * to web browser, uses CGIDEV2 service program               *
 * compile with library CGIDEV2 in library list.              *
 *----------------------------------------------------------- *
 /copy qrpglesrc,hspecs                                        
 /copy qrpglesrc,hspecsbnd                                     
h dftactgrp(*NO) actgrp('SLSRPT1') DATEDIT(*YMD)       
fSLFILE    IF   E             DISK                             
 * Prototype definitions and standard system API error structure             
 /copy qrpglesrc,prototypeb                                                  
 /copy qrpglesrc,usec                                                        
 * Indicators for GetHtmlIfsMult subprocedure                                   
d IfsMultIndicators...                                                          
d                 ds                                                            
d  NoErrors                       n                                             
d  NameTooLong                    n                                             
d  NotAccessible                  n                                             
d  NoFilesUsable                  n                                             
d  DupSections                    n                                             
d  FileIsEmpty                    n                                             
 * Number of variables - use to store quantity of cgi variables passed in       
dnbrVars          s             10i 0                                           
 * Saved query string                                                           
d                 s          32767    varying                                   
 * Create a WorkVariable data structure that can be cleaned up quickly               
d  WorkVariables  ds                                                                 
d @Somevar1                     10i 0 INZ                                            
d @Somevar2                     10a   INZ(' ')                                       
d @Somevar3                       n   INZ                                            
d @Year                          4a   INZ                                            
 * Clear work my variables                                                           
c                   clear                   WorkVariables                            
 * Read externally defined template file. (We could actually use the getHtmlifs      
 * procedure here becuase we are only using one template file.)                      
c                   eval      IfsMultIndicators = gethtmlifsmult(                    
c                             '/myIFSdir/salesReportIFS.html':                            
c                             '<as400>')                                             
 * Get parameters passed from CGI program                                            
 * (this is not required, as we are not passing in any parameters in this example)   
c                   eval      nbrVars =                                              
c                             zhbgetinput(savedquerystring:qusec)                    
 * clear any HTML output that has been buffered but not sent to the browser          
 * (not actually required here, but can be useful in some cases)                     
c                   Callp     clrhtmlbuffer                                          
c                   Callp     wrtsection('Top')                                      
c                   Callp     wrtsection('Header1')                                  
c                   Callp     wrtsection('DataTop')                                  
c                   Read      SLFILE                                                 
c                   Dow       not %EOF                                               
c                   Callp     updHTMLvar('Region' :REG07)                            
c                   Callp     updHTMLvar('Sales' :%char(SLS07))                      
c                   Callp     wrtsection('DataDetail')                               
c                   Read      SLFILE                                                 
c                   Enddo                                                            
c                   Callp     wrtsection('DataBottom')                               
c                   Callp     wrtsection('End')         
c                   Callp     wrtsection('DataBottom')                          
c                   Callp     wrtsection('End')                                 
 * *fini is special section sendsthe output buffer to the web client            
c                   Callp     wrtSection('*fini')                               
c                   Eval      *INLR = *ON                                       
c                   Return                                                                                   

The code above illustrates the process of setting up an HTML page that the user will ultimately see on his browser. The first things that you may notice are some familiar RPG opcodes (setll, reade, dow, enddo) used to read the Sales file (SLFILE). Note that you could as easily have used RPG with embedded SQL here if that is your preference.

Next if you look carefully you will also see some CGIDEV2 procedures.

The gethtmlifsmulti() procedure acts as the link between the RPG program and the HTML template. This procedure indicates to the RPG program, the location of the HTML template code. In this case the HTML template code will be located in IFS library /myIFSdir and will be called salesReportIFS.html. It is also here that we establish the <AS400> tag as the indicator for a new named section header.

The updHTMLvar() procedure serves to update the HTML substitute variables Region and Sales in the HTML template with the current contents of physical file fields REG07 and SLS07.

The procedure wrtsection() writes the named sections to a buffer that contains the web page that we are currently building. We are writing these named sections: Top,Header1, DataTop, DataDetail, DataBottom and End.

Wrtsection() is similar conceptually to the EXCEPT or WRITE opcodes in RPG. When the page is completed we will write the special section called *fini which will trigger the page to be pushed out to the user’s browser.

Now if all goes well then the finished web page should look something like the example on the top of the page. This is a trivial example, however it illustrates how the CGIDEV2 toolkit and a small investment in time can help you bring your iSeries programs to the web. Much more information can be found here including a comprehensive index.

Some CGIDEV2 vocabulary

getHtmlifs This subprocedures allows program to load a single externally defined html IFS (stream) file into program memory. These files are used as html templates.
getHtmlifsMult This subprocedures allows to load into memory multiple externally defined html files. All the sections and records in all the files are read into dynamic storage as though they resided in a single file. If a section name appears more than once, only the first occurrence is used. This feature allows to maintain as separate HTML files frequently used pieces of HTML code, such as headers, footers, navigation bars, etc.. In several cases, breaking html code into separate modules may greatly reduce both development and maintenance times.
zhbgetinput ZhbGetInput uses the server's QzhbCgiParse API to get the browser's input and places it into a set of internal, dynamically allocated arrays for subsequent high performance use by the ZhbGetVarCnt, ZhbGetVar, ZhbGetVarUpper input variable parsing procedures.
clrHtmlBuffer This subprocedure clears any HTML output that has been buffered but has neither been sent to the browser nor written into a stream file. This is useful when program logic dictates you need to output something other than what has already been buffered.
wrtsection Subprocedure to write html sections and send the output buffer. One can write one or multiple sections in a single call. Once all sections have been written, send the output buffer to the client. This must be done by writing a pseudo section named *fini.
updHTMLvar This subprocedure assigns a value to all the instances of a given html variable name. The value to be assigned must be a character string (numeric fields must be converted or edited to character strings.
updHtmlVar2 For vary large output variables (max 16 Mb) you may use subprocedure updHtmlVar2. In this subprocedure, instead of passing the name or the value of the substituting variable, you will pass a pointer to it and its length.
zhbgetvar Parsing procedure zhbgetvar lets you retrieve fields from the input string one at a time into program-defined fields. If you want to uppercase a field while retrieving it from the input string, you may use the parsing procedure zhbgetvarupper. If you want to lowercase a field while retrieving it from the input string, you may use the parsing procedure zhbgetvarlower.
WrtHtmlToStmf/ AppHtmlToStmf Writes or appends buffered output to an IFS stream file instead of responding to a web client. This may useful when: 1) output file is frequently accessed 2) file generation requires significant processing time, thus providing a rather long response time and possibility of client side timeouts 3) contents may change at unpredictable times 4) job should be scheduled at specific time(s).