IBM i Modernization - The User Interface
(Part 14)
This piece is a continuation of the topic of using HTML, CSS, and ILE RPG to produce nicely-formatted, stylized output, which is ready for print. Please review Part 13 to see where this article picks up from.
Please click on the following screen shot to open the PDF file which I generated for this article. There's also an HTML version here.
How Does it Work?
The report is based on an HTML template that looks something like the following (click on the screen shot to view the template in your browser):
Double curly braces (i.e. {{CITY}}) denote "markers" where data will be inserted from an ILE RPG program at runtime.
The HTML template is divided into five (5) sections that are delimited by "RECORD" names. Let's take a look at each section, one by one:
The first section (RECORD:BEGIN) marks the beginning of the HTML page. Note that CSS styles are imported into the page at runtime. It helps to place HTML and CSS in separate files. That keeps the code looking tidy, and provides a basis for sharing a single common style sheet across many reports.
<html>
<head>
<style>@import url("/rdweb/info3/iui14/report.css");</style>
</head>
<body>
The next section (RECORD:PAGEHEAD) marks the beginning of the page heading which consists of a "city" marker, the report title "Entities" and a "date" marker which will be replaced with the date that the report is generated.
Take special note of the class="pagehead" <div> attribute. That is a key to understanding how page headings are automatically repeated in the top margin of every page, which I'll explain further down.
<div class="pagehead">
<table width="100%" class="mg-5">
<tr>
<td align="left" width="300" valign="top">City: <b class="cl-1">{{CITY}}</b></td>
<td align="center"><h1 class="cl-1">Entities</h1></td>
<td align="right" width="300" valign="top">Date: <b class="cl-1">{{DATE}}</b></td>
</tr>
</table>
</div>
The next section (RECORD:DETAIL) marks the beginning of the report's "detail" band. It holds markers for "name" and "address" that will be retrieved from an IBM i database table at runtime and inserted into the report.
<div class="il fl h2 mg10 pbn">
<div class="w4 hi-row pd-5">{{NAME}}</div>
<div class="w4 pd-5 bs-1 h1">{{ADDR}}</div>
</div>
The next section (RECORD:CITY) marks the beginning of the report break that includes a summary count of the number of "entities" in a city.
<div class="mg-5 pba" style="clear:left">City Entities: <b class="cl-1">{{COUNT}}</b></div>
The next section (RECORD:END) completes the layout and includes a marker for a total count of all entities in the database.
<div class="mg-5" style="clear:left">Total Entities: <b class="cl-1">{{COUNT}}</b></div>
</body>
</html>
That completes the HTML template. It's only 27 lines of code and quite digestible. Let's take a look at some of the CSS entities and attributes that are pertinent to printer-ready output.
The CSS Style Sheet
The first thing to note is the "pagehead" class attribute that declares a "position" of "running(header)". That is another key to getting page headings to automatically appear at the top of each page.
position:running(header);
}
There's quite a bit of automation built in to the rather terse declarations shown below:
- A top margin of 1 inch and all other margins of one half inch on each page.
- Each page is standard letter size (8.5 inches by 11 inches).
- Page orientation is portrait (landscape is an option).
- Page headers are centered in the top margin.
- An automated page count is centered in the bottom margin.
- Other layout criteria.
margin:1.0in .5in .5in .5in;
size:letter portrait;
@top-center {
content: element(header);
vertical-align:bottom;
}
@bottom-center {
font-size:16px;
content: "Page " counter(page);
vertical-align:middle;
}
}
The "pba" class is assigned to an HTML element (city total count in this case), which causes a forced page-break to occur after a summary count is output.
page-break-after:always;
}
Browsers and other rendering engines may be asked to avoid page-breaking on any element that references the "pbn" class. This happens to be one instance where the output from our PDF generator is slightly out of sync with recent browser versions. But the difference wasn't a big concern.
page-break-inside:avoid;
}
The CSS file encapsulates other attributes that modify the look and layout of the page. For brevity sake, I shall limit my commentary to the attributes that pertain specifically to printing.
There are other CSS options that provide further print control such as front and back printing which I didn't need in this report. So that's about it.
The ILE RPG Program
The RPG code is delineated as follows:
- Imports procedure prototypes pertaining to SQL cursors and stream file output.
- Declares variables having module-level scope.
- Generates an SQL result set (cursor).
- Loads an HTML template into memory.
- Opens an IFS stream file for output.
- Fetches rows from the SQL cursor within a loop.
- Outputs report details.
- Outputs summary counts and page breaks when the city changes.
- Outputs final entity counts.
- Invokes a procedure to generate a PDF version of the report.
- Performs clean up and ends the program.
It's noteworthy that page footers and page numbers are automatically handled by CSS properties without the RPG programmer having to code for it.
This piece introduces a stream-file API that renders formatted output based on a template (HTML in this case). The syntax is nearly identical to the API that I use for generating HTTP streams for browsers.
Please review the complete RPG source. I hope it makes sense.
//----------------------------------------------------------------- // procedure prototypes //----------------------------------------------------------------- /copy *libl/qrpglesrc,rdstmapi#1 /copy *libl/qrpglesrc,rdcsrapi#1 //----------------------------------------------------------------- // module level data //----------------------------------------------------------------- d c1 s * inz d s1 s * inz d f1 s * inz d city s 40a varying d save_city s 40a varying d city_count s 5u 0 inz d entity_count s 5u 0 inz /free //----------------------------------------------------------------- // generate an SQL cursor //----------------------------------------------------------------- csrInit(); c1 = csrNew('TLOC100P':'CITY, NAME, GADDRSTR AS ADDR'); csrSetOrder('CITY, NAME'); csrRefresh(); //----------------------------------------------------------------- // load HTML template into memory and open a stream file //----------------------------------------------------------------- stmInit(); s1 = stmFmtLoad('IUI14'); f1 = stmFileOpen('/rdweb/info3/iui14/entities.html'); //----------------------------------------------------------------- // begin report //----------------------------------------------------------------- stmRecWrt('BEGIN'); //----------------------------------------------------------------- // fetch cursor rows (main loop) //----------------------------------------------------------------- dow csrGoto(csr_next); exsr check_city; exsr write_detail; enddo; //----------------------------------------------------------------- // finish report (output final entity count) //----------------------------------------------------------------- eval city = 'TOTALS'; exsr write_city; stmRecSet('END'); stmVarSet('COUNT':%char(entity_count)); stmRecWrt('END'); //----------------------------------------------------------------- // generate PDF document //----------------------------------------------------------------- stmToPDF(0); //----------------------------------------------------------------- // end program //----------------------------------------------------------------- csrTerm(); *inlr = *on; return; //----------------------------------------------------------------- // check to see if city has changed //----------------------------------------------------------------- begsr check_city; city = csrColStr('CITY'); if city = save_city; leavesr; endif; save_city = city; exsr write_city; endsr; //----------------------------------------------------------------- // output entity count and page break on change in city //----------------------------------------------------------------- begsr write_city; if city_count > 0; stmRecSet('CITY'); stmVarSet('COUNT':%char(city_count)); stmRecWrt('CITY'); city_count = 0; endif; stmRecSet('PAGEHEAD'); stmVarSet('CITY':city); stmVarSet('DATE':%char(%date():*mdy)); stmRecWrt('PAGEHEAD'); endsr; //----------------------------------------------------------------- // write detail //----------------------------------------------------------------- begsr write_detail; stmRecSet('DETAIL'); stmVarSet('NAME':csrColStr('NAME')); stmVarSet('ADDR':%trimr(csrColStr('ADDR'))); stmRecWrt('DETAIL'); city_count += 1; entity_count += 1; endsr; /end-free
HTML and CSS Tips
I use a utility that transforms HTML into a PDF file. It is based on an open-source product named iText which renders output somewhat differently than browsers. The PDF version is better in some respects (page layout is more precise and better fitted for print). However there are a number of things that may be supported in browsers but not in PDF rendering (and visa-versa).
HTML must follow XML standards that enforce the use of beginning and ending tags (XML must be well-formed). I often use the stand-alone <br> tag in web pages to implement line breaks after text. But that makes a mess when attempting PDF generation. You can use <p></p> instead.
When using CSS styles for layout (i.e. height, width, padding, margins, etc.) browsers default to measurements in pixels when the unit of measure is not explicit in CSS properties. Units of measurement (i.e. pixels, inches, em's) must be explicitly stated in CSS styles for proper PDF rendering.
Some CSS3 effects may be supported in browsers but not PDF rendering. For example, in this case I used a border shadow effect that was not rendered in the PDF version. The PDF output still looked good. So I didn't try an alternative style.
Conclusions
HTML and CSS provide for nicely-formatted and stylized printing. The APIs I've shown in this piece perform this with remarkable efficiency in RPG programs.
Skills with these technologies make generating output like this within the reach of IBM i developers.