Skip to content

Visual Report Writer and The Web (V)

In this fifth blog about Visual Report Writer and The Web, I will discuss the next report available on the Live Demo website (European Server, USA server) named OrderList. If this is the first blog you are reading, I encourage you to check out the previous four blogs: 1: The Solution, 2: Invoices Report, 3: The Cleanup, and 4: The CustomerList. Since the fourth blog, we have released the Alpha I version of Visual Report Writer 3.0 and the 2.1+ Library only setup. The latter is necessary for easy web reporting using the 17.1 DataFlex Web Framework, as demonstrated on the demo website.

The Report

The report used in this example web view is delivered with the 17.1 WebOrder example workspace. It demonstrates how to utilize colors (with different colors for the page header and group header), how to display selection criteria (assuming the report filters only on order total), and how to concatenate table columns into a new calculated column using a function. Additionally, the report includes two summaries placed in the group footer.

The Integration

At the integration level, this specialty includes two features not yet covered in the other blogs: the user can select the report order in a multi-segment manner, and the result (PDF) is displayed in a modal dialog.

The Ordering Selection

From a Visual Report Writer perspective, this integration feature is not very complex, as you can set the record ordering by either expanding or replacing the current ordering. Replacing requires removing the current ordering, which is done via:

Send RemoveAllRecordSortFields sReportId

The sReportId is a GUID returned by an OpenReport instruction. The report ID (required) may also be passed to the function as an empty string (or C_USEMAINVRWREPORTID), in which case the integration library uses the psReportId property of the cVRWReport class.

Adding record filter instructions can be accomplished by sending the AddRecordSortField message. The user-selected sort fields in this example view can be retrieved by calling a function named SortColumnSet from the grid. This function returns a two-dimensional string array containing the sort field names and their sort direction (ascending/descending). The returned information can be enumerated, and each row in the array can be used to set the record sort order. The code in this example is:

Procedure SetSortFields
    String[][2] sSortColumnData
    Integer iElements iElement

    Send RemoveAllRecordSortFields C_USEMAINVRWREPORTID
    Get SortColumnSet of oSortColumnsGrid to sSortColumnData
    Move (SizeOfArray(sSortColumnData)) to iElements
    Decrement iElements
    For iElement from 0 to iElements
        Send AddRecordSortField C_USEMAINVRWREPORTID sSortColumnData[iElement][0] sSortColumnData[iElement][1]
    Loop
End_Procedure

Now, let us examine the control that allows the user to select the columns for the sort ordering. First, the end-user cannot select which columns to sort on; they can only set the sort order among the available columns, which are determined by the report integrator (you). The grid has two columns: one for the name of the sort column and one for selecting ascending/descending.

Object oSortColumnsGrid is a cVRWWebSortColumnsGrid
    Object oSortOnWebColumn is a cWebColumn
        Set psCaption to "Sort On"
        Set pbSortable to False
        Set piWidth to 90
        Set pbEnabled to False
    End_Object

    Object oSortDirectionWebColumn is a cVRWWebSortDirectionColumn
        Set psCaption to "Direction"
        Set pbServerOnChange to True

        Procedure OnChange String sNewValue String sOldValue
            Forward Send OnChange sNewValue sOldValue
            Send ProcessDataSet of oSortColumnsGrid 3
        End_Procedure
    End_Object

    Procedure OnSetSortColumns tWebRow[] ByRef WebSortColumnRows
        Move "{Orderdtl.Detail_Number}" to WebSortColumnRows[0].aValues[0]
        Move "Detail Nr" to WebSortColumnRows[0].aValues[1]
        Move C_VRWAscending to WebSortColumnRows[0].aValues[2]

        Move "{Orderdtl.Item_Id}" to WebSortColumnRows[1].aValues[0]
        Move "Item ID" to WebSortColumnRows[1].aValues[1]
        Move C_VRWAscending to WebSortColumnRows[1].aValues[2]

        Move "{Orderdtl.Qty_Ordered}" to WebSortColumnRows[2].aValues[0]
        Move "Qty Ordered" to WebSortColumnRows[2].aValues[1]
        Move C_VRWAscending to WebSortColumnRows[2].aValues[2]
    End_Procedure
End_Object

The grid object is instantiated from the cVRWWebSortColumnsGrid class, which is a subclass of cWebGrid. The grid subclass is not read-only and is not a cWebList because the user must be able to select the sort direction for each of the columns. New grid rows cannot be added by the end-user by setting pbAllowAppendRow and pbAllowInsertRow to false. Users cannot remove columns by setting pbAllowDeleteRow to false. These properties are set in the construct_object event.

The sort columns are loaded in an event named OnSetSortColumns. This event is triggered from OnManualLoadData (a documented event of the cWebGrid class). The OnManualLoadData is marked as private for the cVRWWebSortColumnsGrid class and should not be used by you. The grid data is stored in both the grid and a web-synchronized property that contains a comma-separated value list of the grid data. This list is maintained to simplify retrieving the user's choice for the sort order. The SortColumnSet function (used from SetSortFields) converts this comma-separated list into a two-dimensional array of sort columns.

As seen in the code above, the grid appears to have three columns since three values are provided for each grid row in the tWebRow[] argument. However, the first value (the [0] value) is a unique row identifier. In a data-aware grid (such as an order detail grid), it contains the serialized RowId of the database table row. In this non-data-aware grid (pbDataAware is false and peDbGridType is gtManual), the unique value is the table.column reference as Visual Report Writer expects. This makes the first real grid column just a display value column, and you can place anything you like there, provided the end-user can make decisions based on this value.

If you wish to translate the terms "Ascending" and "Descending," you can set the properties psAscendingText and psDescendingText of the sort direction column (class is cVRWWebSortDirectionColumn). By the way, I noticed that Google translated the values last week, and mysteriously, the control continued to function.

Changing the sort order can be done by clicking one of the buttons on the side. These controls are cWebButtons but do not appear as buttons (thanks to CSS). If the end-user clicks one of the buttons, a ProcessDataSet message is sent to the grid. The ProcessDataSet method is augmented in the class to correct the piCurrentRowIndex if needed and then sent to the client. The client, in turn, sends an OnProcessDataSet to the server. Depending on whether the user clicked the up or down button or changed the sort direction drop-down value, the OnProcessDataSet modifies the contents of the grid's data (passed as a tWebRow[] argument). After changing the data, a grid refresh instruction is sent to the client. Data is only modified if a sort column can be moved up or down in the order or if the user changes the sort direction.

The good news about this rather complex grid control is that you never have to code this yourself if you are using the integration wizard. During the wizard, you can select that the user should be able to choose the sort order and that a multi-value sort order is desired, and the entire grid (plus the buttons) will be generated. An important complexity of this grid is that the messages are, due to process pooling, most likely not handled by the same server process. It is all managed for you, but be aware if you wish to make changes.

The Modal Display

The results of the report are written to a PDF file on the server, similar to the customer report and a few other reports on this demo website. The PDF is not displayed in an IFrame, a different window/browser, or on a second tab page; instead, it is shown in a modal dialog. The integration library includes a web object component that can be included in your web application for displaying these files. The component file name is PDFModalDialog.wo. This component can be included in the report component or inside webapp.src, where all other web components are included. In either case, the dialog becomes a sibling of the cWebView, as it should. And the really good news is that the integration wizard can do this for you!

Use PDFModalDialog.wo

The component defines an object of the cWebModalDialog class with two panel children: one to display the results and another to organize the location of the button to close the dialog.

You use the dialog by sending the message ShowPDFModal to the object, passing the object to return to when the dialog closes, the object handle of the cVRWReport object, and a caption text to be displayed in the caption bar of the modal dialog.

Object oRunReportMenuItem is a cWebMenuItem
    Set psCaption to "Run Report"
    Set psTooltip to "Run the report with selections"
    Set psCSSClass to "VRWRunReportButton"

    Procedure OnClick
        Send ShowPDFModal of oPDFModalDialog Self oReport "OrderList (PDF)"
    End_Procedure
End_Object

As you can deduce from the above code, you are not passing the name of the PDF file but sending the instruction to create the report PDF and display it. The ShowPDFModal method sends a message RunReport, which in turn sends a GenerateReport message to the cVRWReport object. Thus, the cVRWReport object (inside the cWebView component) contains the logic to collect the selection criteria, sort orders, etc., generate the PDF file, and return the URL to the PDF file to the caller (the dialog).

The GenerateReport method inside the cVRWReport object is the same as shown before, but to make it easy for my readers, here is the code:

Function GenerateReport Returns String
    String sReportId sFile
    vrwPDFExportOptions PDFReportOptions
    Boolean bCanceled

    Get OpenReport to sReportId
    If (sReportId <> "") Begin
        Get DefaultPDFExportOptions to PDFReportOptions
        Set pPDFExportOptions to PDFReportOptions
        Get ReportCacheFileName ".pdf" to sFile
        If (sFile <> "") Begin
            Send ExportReport C_vrwPDF sFile
        End
        // Check if report ran ok; if not, return empty path
        Get pbCanceled to bCanceled
        If bCanceled Begin
            Move "" to sFile
        End
        Send CloseReport of oReport sReportId
    End
    Function_Return sFile
End_Function

This concludes the fifth blog about Visual Report Writer and the web. Stay tuned for more blogs to follow in the coming time.