One of the most important parts of any application is the output of various reports/documents - for example, invoices, customer statements, shipping manifests, etc. Many developers produce high quality output by using pre-printed forms and having their DataFlex application fill in the blanks to create a high quality customer facing document.
The problem, however, is that these forms can be expensive to create/modify, purchase, and store. If the customer wants even a minor change to the documents layout, an expensive redesign process is generally required. Also, money spent on the existing pre-printed forms is essentially wasted once the forms are changed.
An excellent solution to this is to use DataFlex Reports to generate these documents. The advantage is that all of the formatting is included in the DataFlex Report and layout changes or even wholesale redesign can be done without a tremendous amount of work. Also, since the report includes all of the formatting, there is no need to stock custom forms. This saves in costs, storage, and actually simplifies the application as these documents can now be sent to any printer, as opposed to having to be redirected to special printers loaded with pre-printed forms.
Below are 2 images. The first is of a customers pre-printed form based document, and the second of the DataFlex Reports result. As you can see, the quality of the DataFlex Reports document is very high and serves as an ideal replacement for the pre-printed form version of the document.
Figure 1: This is what their pre-printed form output looked like
As you can see above, the pre-printed form has some graphics at the top and areas of shading. The text printed is low quality and non-proportional - it doesnt look the greatest
Figure 2: This is the output from DataFlex Reports
Figure 2 shows the output of the DataFlex Reports version. The customer had wanted the shading removed, but as they had so many pre-printed forms in stock, they continued to use the same forms. Once we converted to DataFlex Reports, we were able to customize the layout for the customer and can continue to do so as their needs change.
Notice the DataFlex Reports version uses a much nicer looking proportional font, uses actual lines instead of ASCII characters for borders, and still includes the customers document specific graphics at the top.
The process of this conversion can be broken down into 3 steps:
In many cases, the customer will have graphic files that were used to design the pre-printed forms. In some cases, you may need to contact whoever developed the pre-printed forms to get their graphics. In the worst case, you would need to recreate a graphic or scan one in of high quality and use it in your DataFlex Reports layout.
For our invoice, we needed the graphics used at the top of the page. The customer sent us these graphics so that we could incorporate them into our report. See the images below of the graphics sent to us by the customer.
Figures 3, 4, & 5: Required logos
We used JPEG versions of these logos as that format looks good when printed/viewed and isnt as large as other graphical formats like TIFF or BMP. The customer had these graphics available so it was easy to incorporate them into our report. Had they not had these available, they would have to have been scanned in from their pre-printed form or the company who created the pre-printed forms might have had to be contacted to get the graphical files from them.
The next part of the process was designing the report layout itself. The first thing we did was create the header of the report which included the graphics above.
Figure 6: Layout of the header section in DataFlex Reports
As you can see above, the layout includes some static text (like the address information to the right of the Colorado logo), some graphics (the Colorado logo, Accredited Door Dealer logo, and the International Door Association logo) and some field values (like the Invoice # (header.inv). We used the layout capabilities of DataFlex Reports to position the logos where we wanted them, resized the logos as needed, and inserted any static text along with record data. This is where we are essentially recreating the design of the pre-printed form.
Figure 7: Inserting an image into our DataFlex Report
Inserting an image is quite simple. You just select the insert option from the menu and choose image. You are then presented with a file locator to let you select which image to insert into the report. When doing so, you have the option of embedding the image in the report, or having the image loaded from a specific location at run time. We embedded our images so no outside files would be needed. However, the runtime option is good if some of the images may change but the rest of the report wont. That allows the image file to be updated at any time, and the report will then use the new image. Of course, this assumes the image name and location remain the same after the change.
Figure 8: Using Lines to format the layout
As our form needs vertical and horizontal lines to match what they had in their pre-printed form, we use line objects in DataFlex Reports. You can see above where we have some horizontal and vertical lines. We can choose the width and height of the lines to make an attractive layout.
Figure 9: Inserting a line object to create a nice looking layout
As you can see above, you simply select the insert menu option and choose the Line object type. You can then position the line in your report and set its properties to select its width, color, etc.
Figure 10: Setting a line objects appearance
If you select the line within DataFlex Reports, a dialog will appear allowing you to change the lines style, width and color. Above we have selected the horizontal line under the Deposit field in the footer section. We set its width to 2 points so that it is thicker than the other horizontal lines and stands out.
When creating a DataFlex Report, you can have the report talk to the database source directly, or pass the data to the report using a technique called Runtime Data Source (RDS). With RDS, your DataFlex application contains an array of structures, each representing a logical record to be output in the report. In many cases, you will be supplying 2 logical tables: a header table and a line item table. In our examples above, we use 2 tables. Table 1 is the invoice header and footer information and Table 2 are the line items.
When creating the DataFlex Report using the report wizards, you select Runtime Data Source as the data source instead of choosing physical tables. Below is an image showing this option being selected.
Figure 11: Selecting the data source via the DataFlex Reports wizard
After you choose Runtime Datasource, you then create one or more logical tables based on the data being reported. In our invoice example, we create 2 tables: the invoice header/footer table and the line item detail. See below for screen shots of the RDS tables being created.
Figure 12: The Header logical table is defined
Figure 13: The Detail logical table is defined
To determine what fields we needed to add to our logical RDS files in DataFlex Reports, we reviewed the existing basic report object code in the application to see what values are being output. In the case of the detail logical table, this is essentially information from the Body section. For the header logical table, this is information that appears at the top of the invoice and the bottom of the invoice. We could have created 3 tables: header, footer, and detail, but we easily combined the information for the header and footer of the invoice into a single logical table (Header) for simplicity.
Here is some code from the Basic Report objects body section:
Print CPINLINE.QTY_SOLD to PRTCPNN1_BODY.1
Print CPINLINE.ITEM_DESCR_1 to PRTCPNN1_BODY.2
If CPINLINE.BACKORDER_FLAG EQ "Y" Print "Y" to PRTCPNN1_BODY.3
Print CPINLINE.ITEM_UNIT_PRICE to PRTCPNN1_BODY.4
CALC (CPINLINE.QTY_SOLD * CPINLINE.ITEM_UNIT_PRICE) to nExtd_Price
If CPINLINE.BACKORDER_FLAG EQ "Y" Move 0 to nExtd_Price
If you compare the code above with figure 13 above, you will see the fields we created for the logical Detail table match the data being output in the Body section of the Basic Report object we were previously using. The code below is used to create the RDS data structure to be sent to the report when run. This is the AddRDSData function where it is up to us to put in the application specific code to populate the RDS structures.
Get piInvoiceNum of oserviceinvoiceReportView to iInvoice
Move iInvoice to CPINLINE.INV_NBR
Find gt CPINLINE by Index.1
[Found] indicate Found as cpinline.inv_nbr eq iInvoice
Move our_invoice to vData[iRow]
Move CPINLINE.QTY_SOLD to vData[iRow]
Move CPINLINE.ITEM_DESCR_1 to vData[iRow]
If SYSFILE.ITEM_LINE eq "2" Move CPINLINE.ITEM_DESCR_2 to vData[iRow]
Else Move "" to vData[iRow]
If CPINLINE.BACKORDER_FLAG EQ "Y" Move CPINLINE.BACKORDER_FLAG to vData[iRow]
Else Move "" to vData[iRow]
Move CPINLINE.ITEM_UNIT_PRICE to vData[iRow]
Move (CPINLINE.QTY_SOLD * CPINLINE.ITEM_UNIT_PRICE) to vData[iRow]
If CPINLINE.BACKORDER_FLAG EQ "Y" Move 0 to vData[iRow]
Until [not Found]
The RDS data is sent via a 2 dimensional array. The first dimension of the array is the record counter and the second dimension contains an element for each piece of data being sent to the DataFlex Report. The order of the data in the second dimension matches the order of the logical fields as defined in the DataFlex Reports RDS screen.
In our example above, we are filling in the detail records to send to the DataFlex Report. We simply look through all records in our physical line item table (CPINLINE) that have the same invoice number as the invoice we are going to print. As we find each line item record, we create a new array element (iRow is the counter used) and populate the second dimension with data from the CPINLINE table.
The code below is a snippet of the code used to populate the Header logical table. The order of the elements for dimension 2 follow the same order as the Header RDS table created in DataFlex Reports.
Move CPINEHDR.INV_NBR to vData[iRow]
Move CPINHEDR.CUST_NBR to vData[iRow]
Move CPINHEDR.JOB_SITE_NR to vData[iRow]
Move CPINHEDR.DATE_COMP to vData[iRow]
Move CPINHEDR.INVOICE_DATE to vData[iRow]
One nice thing about the DataFlex Reports integration wizard is that it will create stub code for the AddRDSData function in the correct order based on the reports RDS logical tables. So, you essentially replace each stub line with the proper code to update the vData array. Note, in our example above, we are only creating 1 header record to pass as this report prints just one invoice. Therefore, in the above code, iRow is always 0.
When creating the actual report layout in DataFlex Reports, you drag fields from the field explorer onto the report just as if the fields were from physical tables instead of logical tables. When the runtime invokes the DataFlex Report, it sends the vData info created in the above 2 pieces of code to the report to use as its data.
In our application, we created a button to invoke the report. The code is as follows:
Send RunReport Of oReport
When RunReport is sent to our oReport object, the AddRDSData function is called to populate the RDS data structure. Our code above is invoked to put the correct values into the Header and Detail logical records, and then our report is invoked, passing the RDS data to the DataFlex Report.
The application knows which DataFlex Report to invoke by setting the psReportName property of the cDrReport object.
Set psReportName of (oReport(oserviceinvoiceReportView)) to invoice.dr
If in the future we want to make changes to the report layout, all we need to do is to edit the report in DataFlex Reports. No changes to code would be needed. However, if we want to add additional data to the report, we would need to expand the fields in the RDS definitions of header or detail, and then modify our AddRDSData function to fill in the new pieces of data in the vData array. Since most changes to these types of documents are normally just visual (changing a bitmap, doing some general layout work, etc.), it is often the case that we dont need to make any changes at the application level and just update and redeploy the DataFlex Report file (the .dr file).
Another nice feature of this implementation is that if we have multiple clients who want customized documents, we can edit that customers DataFlex Report without needing to change the main application (assuming no additional data is needed for the RDS data source). This allows a power user at the customer site to make customizations to their documents without needing to learn to code in DataFlex. It also means we dont need to maintain multiple customized source code components as the main application source remains the same for all customers, even those with customized report layouts.
Note, the technique described works very well for documents which are customer facing and need to be high quality. However, internal reports are often kept as Basic Report objects with plain output, as the expense of converting these reports to DataFlex Reports may not be justified if the target audience is internal employees who are OK with seeing a plain report output. The decision about which reports to convert and which to keep as before will depend on the target audience of the documents and your customers budget.
To deploy, you just need to install the DataFlex Reports runtime files (which are no-cost) to the customers network. If the customer will be modifying these reports, the customer will need the Developer Edition of DataFlex Reports (in fact, each customer who will be maintaining their own reports will need their own Developer Edition of DataFlex Reports).