|
Enhancing CGIDEV2
Published: November 4, 2009
by Paul Tuohy
Maybe "enhancing" is too strong a word, but "Add-Ons for CGIDEV2" doesn't have the same ring to it. I have always been (and still am) a big fan of CGIDEV2. A tool, such as CGIDDEV2, must be flexible enough to cater for all tastes. But when you have nailed down your own standards and working methods it is sometimes easier to have add-on procedures to implement those standards and working methods.
With that in mind, I want to share a few of the subprocedures I have written that make CGIDEV2 a little easier to use in my development environment. All of these subprocedures are coded in a single module: UTILCGI. Although this article deals specifically with CGIDEV2, it also contains examples of how you can wrap functionality around existing subprocedures.
Loading HTML Documents
When it comes to the HTML documents that I use in a Web application, there are a number of points worth noting:
- Each document must have the content type text/html entry at the start of the document.
- The header and footer section of each Web page is the same.
- The use of buttons (add, change, delete, etc.) is the same throughout my application.
- I prefer not to use the default identifiers for section names and variable names. Instead I use <!-- $section$ --> instead of /$section and <!-- %variable% --> instead of %variable%.
- I maintain the identity of the directory containing the CGIDEV2 template documents in a data area.
CGIDEV2 provides two main subprocedures for loading HTML documents: getHTMLIFS() and getHTMLIFSMult(). The getHTMLIFSMult() subprocedure allows an easy means of loading multiple HTML documents to be processed as one. This is the equivalent of having copy members in DDS. Example Code 1 below shows the content of the template document StdMaintHead.html. This document contains a standard header that is common to all Web pages created interactively by an RPG program in my application. The main points to note are:
- The content type text/html entry is at the start of the document. This means that the content type is defined in only one template document in my application.
- There are two sections in the document, in case a program needs to insert additional heading information.
- There are entries to include a standard CSS document and a standard Javascript document.
- Documents make copious use of divisions and CSS classes. I leave it up to you to decide on your own style.
Code 1: The StdMaintHead.html template document
<!-- $top$ -->
Content-type: text/html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=ISO-8859-1">
<title>My Application</title>
<script language="javascript" src="/js/entryRoutines.js"
type="text/javascript" />
<link rel="stylesheet" href="/css/main.css" type="text/css" >
<!-- $top2$ -->
</head>
<body">
<div id="container">
<div class="logo">My Logo
</div>
<div class="pageHeading"> My Common Headings
</div>
Code 2 shows the content of the template document StdMaintFoot.html. This document simply contains a section that completes the Web page.
Code 2: The StdMaintFoot.html template document
<!-- $end$ -->
</div>
</body>
</html>
Code 3 shows the content of the template document StdButtons.html. This document contains sections for each permutation and combination of submit buttons used throughout an application.
Code 3: The StdButtons.html template document
<!-- $changebutton$ -->
<div class="buttons">
<input type="submit" name="cgioption" id="CheckButton" value="Change">
<input type="submit" name="cgioption" value="Delete" onClick=
"return confirmDelete()">
<input type="submit" name="cgioption" value="Cancel">
</div>
<!-- $addbutton$ -->
<div class="buttons">
<input type="submit" name="cgioption" id="CheckButton" value="Add">
<input type="submit" name="cgioption" value="Cancel">
</div>
</pre>
<!-- $changeCancelButton$ -->
<div class="buttons">
<input type="submit" name="cgioption" id="CheckButton" value="Change">
<input type="submit" name="cgioption" value="Cancel">
</div>
Finally, Code 4 shows the content of the template document StdCGI.html. This is a very simple template containing a standard section name and variable name that is used in the generation of standard drop-down boxes, radio buttons, and check boxes. But more about that later in the article.
Code 4: The StdCGI.html template document
<!-- $SIDCGISection$ -->
<!-- %SIDCGIVar% -->
Assuming that all of these templates are stored in a directory named MyTemplates in the IFS, I will have a 512-byte data area named CGIROOT, in the library containing my CGIDEV2 programs, with a value of '/MyTemplates/'.
I have wrapped the getHTMLIFSMult() subprocedure in another subprocedure, loadHTMLDocs(), which allows for less coding required in the template document being processed. Code 5 shows the relevant portion of the global D specs in UTILCGI along with the subprocedure loadHTMLDocs(). The main points to note are:
- The subprocedure accepts up to five document names. Only the first is required.
- If the list of standard templates has not been set, the identity of the template directory is retrieved from the data area and the variable HTMLDocs is set to identify the list of standard documents. Note that each standard document has the template directory appended and that HTMLDocs is a static variable so it does not need to be reset ob each call.
- The variable HTMLDocsIn is set to identify the list of documents passed as parameters. Again, each document has the template directory appended.
- Finally, the getHTMLIFS() subprocedure is used to retrieve the documents.
Code 5: The subprocedure loadHTMLDocs()
D CGIRoot s 512a DtaAra(CGIROOT)
D CGIRootDone s n
D thisRoot s 512a Varying
P loadHTMLDocs B Export
D PI
D doc01 50a Varying Const
D doc02 50a Varying Const
D Options(*NoPass)
D doc03 50a Varying Const
D Options(*NoPass)
D doc04 50a Varying Const
D Options(*NoPass)
D doc05 50a Varying Const
D Options(*NoPass)
D HTMLDocs s 32767a Varying Static
D HTMLDocsIn s 1000a Varying
/free
if not CGIRootDone;
in(E) CGIRoot;
thisRoot = %Trim(CGIRoot);
HTMLDocs = thisRoot + 'StdMaintHead.html ' +
thisRoot + 'StdButtons.html ' +
thisRoot + 'stdCGI.html ' +
thisRoot + 'StdMaintFoot.html ';
CGIRootDone = *On;
endIf;
HTMLDOcsIn = thisRoot + doc01;
if %Parms()> 1;
HTMLDOcsIn = HTMLDOcsIn + thisRoot + doc02 + ' ';
endIf;
if %Parms()> 2;
HTMLDOcsIn = HTMLDOcsIn + thisRoot + doc03 + ' ';
endIf;
if %Parms()> 3;
HTMLDOcsIn = HTMLDOcsIn + thisRoot + doc04 + ' ';
endIf;
if %Parms()> 4;
HTMLDOcsIn = HTMLDOcsIn + thisRoot + doc05 + ' ';
endIf;
getHTMLIFSMult(HTMLDocs + HTMLDocsIn :
'<!-- $':'$ -->':'<!-- %':'% -->');
return;
/end-Free
P E
The existing ability in CGIDEV2 to load multiple documents eases the requirement to repeat standard definitions in multiple HTML documents. Encapsulating the loading of these standard documents, along with the processing of an externally defined template directory, further eases the processing requirements in a CGI program.
Selection Lists, Radio Buttons, and Check Boxes
Some of the other items that can be tricky to handle with CGIDEV2 are selection lists, radio buttons, and check boxes. Of course CGIDEV2 has the crtTagOpt() subprocedure, which makes the generation of selection lists much easier and which I make use of in the following subprocedures: setCGIMultiple(), writeCGIMultiple(), and endCGIMultiple().
Let's have a look at these subprocedures at work. These subprocedures make use of the standard document shown back in Code 4. Code 6 shows the relevant portion of a programs template document and Code 7 shows the corresponding program code that uses the template.
Code 6: Template code
<span class="entryQuestion">In List? </span>
<!-- $afterInList$ -->
</p>
<p>
<span class="entryQuestion">Check These</span>
<!-- $afterCheckThese$ -->
</p>
<p>
<span class="entryQuestion">Size</span>
<!-- $afterSize$ -->
</p>
Code 7: Corresponding program code for Template code in Code 6
setCGIMultiple('R': 'InList' : inList :
*Omit : *Omit : 2);
writeCGIMultiple('Y' : 'Yes');
writeCGIMultiple('N' : 'No');
wrtsection('afterInList');
setCGIMultiple('C': 'CheckThis');
writeCGIMultiple('W' : 'Watch' : checkW);
writeCGIMultiple('L' : 'Wallet' : checkL);
writeCGIMultiple('G' : 'Glasses' : checkG);
wrtsection('afterCheckThese');
setCGIMultiple('S': 'Size' : size);
writeCGIMultiple('S' : 'Small');
writeCGIMultiple('M' : 'Medium');
writeCGIMultiple('L' : 'Large');
endCGIMultiple();
wrtsection('afterAllowRpt');
Assuming that program fields have the following values, inList='Y', checkW='W', checkL=' ', checkG='G', and size='M', the above bit of jiggery-pokery will result in the html shown in Code 8.
Code 8: HTML generated from code in Figure 6 and Figure 7
< span class="entryQuestion">In List?</span>
<input type="radio" name="InList"
class="entry" value="Y" checked> Yes
<input type="radio" name="InList"
class="entry" value="N" > No
</p>
<p>
<span class="entryQuestion">Check These</span>
<input type="checkbox" name="CheckThis"
class="entry" value="W" checked> Watch
<br />
<input type="checkbox" name="CheckThis"
class="entry" value="L" > Wallet
<br />
<input type="checkbox" name="CheckThis"
class="entry" value="G" checked> Glasses
</p>
<p>
<span class="entryQuestion">Size</span>
<select name="size" class="entry" >
<option value="S">Small</option>
<option value="M" SELECTED>Medium</option>
<option value="L">Large</option>
</selected>
</p>
I know that one of the major benefits of CGIDEV2 is that HTML is externally defined in a template and that these subprocedures are diverting from that by generating the HTML internally. But remember that the generation of the HTML is to a standard and it is still external to the program that is generating the page.
The subprocedures act as follows:
- setCGIMultiple() constructs the required element.
- writeCGIMultiple() places an individual entry.
- endCGIMultiple() finishes the element (only required for selection lists).
Code 9 shows the base data structure and work fields (defined in the global D specs in UTILCGI) used by the subprocedures.
Code 9: Work fields used by the subprocedures
D base Ds Qualified Inz
D type 1a
D currValue 50a Varying
D tabIndex 10i 0
D accross 10i 0 Inz(1)
D i 10i 0
D data s 1000a Varying
D dataOut s 1000a Varying
D VARNAME C 'SIDCGIVar'
D SECTIONNAME C 'SIDCGISection'
Let's have a look at the subprocedures, starting with setCGIMultiple(), shown in Code 10. The main points to note are:
- The first parameter identifies the required element as a radio button (R), check box (C), or selection list (S).
- The second parameter identifies the name of the variable.
- The third optional parameter identifies the current value for the variable. This is used to set the checked or selected option for the element.
- The fourth optional parameter may be used to set a tab index.
- The fifth optional parameter may be used to set a CSS class attribute--if not specified this defaults to a value of "entry."
- The sixth optional parameter identifies how many elements should be on a line.
- The final option parameter identifies any other attributes that should be assigned to an element, e.g., to call a JavaScript function.
- The subprocedure constructs an element tag based on the value of the first parameter.
- The name attribute is assigned and other attributes are stored or assigned based on whether or not relevant parameters were passed.
- Finally, if the element is a selection list the element is written. If the element is for a radio button or check box, the element is completed with the value and checked options being identified by entries of %vl% and %ch% (these will be replaced in the writeCGIMultiple() subprocedure).
Code 10: The setCGIMultiple() subprocedure
p setCGIMultiple B Export
D PI
D type 1a Const
D variable 50a Varying Const
D currValue 50a Varying Const
D Options(*NoPass : *Omit)
D tabIndex 10i 0 Const Options(*NoPass : *Omit)
D class 50a Varying Const
D Options(*NoPass : *Omit)
D accross 10i 0 Const Options(*NoPass : *Omit)
D other 500a Varying Const Options(*NoPass)
D classDft s 50a Varying
D Inz('class="entry" ')
/free
reset Base;
base.type = type;
select;
when base.type = 'S';
data = '<select ';
when base.type = 'R';
data = '<input type="radio" ';
when base.type = 'C';
data = '<input type="checkbox" ';
other;
return;
endSl;
data = data + 'name="' + %trim(Variable) + '" ';
if %Parms() > 2;
if (%Addr(currValue) <> *null);
base.currValue = %Trim(currValue);
endIf;
endIf;
if %Parms() > 3;
if (%Addr(tabIndex) <> *null);
base.tabIndex = tabIndex;
data = data+'tabIndex="' + %char(tabIndex) + '" ';
endIf;
endIf;
if (%Parms() > 4);
if (%Addr(class) <> *null);
classDft = %Trim(class);
endIf;
endIf;
data = data + classDft;
if %Parms() > 5;
if (%Addr(Accross) <> *null);
base.accross = accross;
endIf;
endIf;
if (%Parms() > 6);
data = data + %Trim(other);
endIf;
if base.type = 'S';
data = data + '>';
updHTMLVar(VARNAME : data);
wrtSection(SECTIONNAME);
else;
data = data + 'value="%vl%" %ch%>';
endIf;
return;
/end-Free
P E
Code 11 shows the writeCGIMultiple() subprocedure. The main points to note are:
- The first parameter identifies the value to be assigned to the element.
- The second parameter identifies the descriptive text for the element.
- The third optional parameter identifies the current value for the element (this should only be required for check boxes).
- If the element is a selection list then the crtTagOpt() subprocedure is used to create the <option> element otherwise.
- The number of entries per line is calculated and either spacing or a line break is placed on the page. Then, the %Replace BIF is used to replace %vl% and %ch% entries with the required values and the element is written.
Code 11: The writeCGIMultiple() subprocedure
P writeCGIMultiple...
P B Export
D PI
D value 50a Varying Const
D desc 100a Varying Const
D currValue 50a Varying Const Options(*Nopass)
D workValue s Like(Value)
D workDesc s Like(Desc)
D workCurrValue s Like(currValue)
/free
workValue = %trim(value);
workDesc = %trim(desc);
if (base.type = 'S');
updHTMLvar(VARNAME:CrtTagopt(workValue :
workDesc :
base.currValue));
wrtsection(SECTIONNAME);
else;
base.i += 1;
if (base.i > 1);
if %Rem( base.i - 1 : base.accross) = 0;
updHTMLVar(VARNAME : '<br/>');
else;
updHTMLVar(VARNAME : ' ');
endIf;
wrtSection(SECTIONNAME);
endIf;
dataOut = %Replace(workValue:data:%Scan('%vl%':data):4);
workCurrValue = base.currValue;
if (base.type = 'C' and %Parms() > 2);
workCurrValue = currValue;
endIf;
if (workValue = workCurrValue);
dataOut = %Replace('checked':dataOut:%Scan('%ch%':dataOut):4);
else;
dataOut = %Replace(' ':dataOut:%Scan('%ch%':dataOut):4);
EndIf;
dataOut = dataOut + ' ' + workDesc;
updHTMLVar(VARNAME : dataOut);
wrtSection(SECTIONNAME);
endIf;
/end-Free
P E
Finally, Code 12 shows the endCGIMultiple() subprocedure. This subprocedure only needs to be called to complete a selection list (but no harm is done if called for any of the other elements). The subprocedure simply writes the required ending </select> element.
Code 12: The endCGIMultiple() subprocedure
P endCGIMultiple B Export
D PI
/free
if (base.Type = 'S');
updHTMLVar(VARNAME : '</select>');
wrtSection(SECTIONNAME);
endIf;
/end-Free
P E
There You Have It
Hopefully this has gotten your creative juices floating and, even if the code is of no direct or immediate use, it might give you some idea of how you can go about adding functionality to existing subprocedures without having to re-write or change them.
If you like this approach to CGIDEV2, you might want to go the full hog and have a look at Rennaisance, which is an open source (i.e., free) framework built around CGIDEV2.
Paul Tuohy is CEO of ComCon, an iSeries consulting company, and is one of the co-founders of System i Developer, which hosts the RPG & DB2 Summit conferences. He is an award-winning speaker who also speaks regularly at COMMON conferences, and is the author of "Re-engineering RPG Legacy Applications," "The Programmers Guide to iSeries Navigator," and the self-study course called "iSeries Navigator for Programmers." Send your questions or comments for Paul to Ted Holt via the IT Jungle Contact page.
Post this story to del.icio.us
Post this story to Digg
Post this story to Slashdot
|