Skip to content

Visual Report Writer and The Web (VIII)

In this eighth blog about Visual Report Writer and the Web, I want to take you to the next report available on the Live Demo website (European Server, USA server) named Sick Leave. If this is the first blog you are reading, I encourage you to read the previous seven blogs (1: The Solution, 2: Invoices Report, 3: The Cleanup, 4: The CustomerList, 5: The OrderList, 6: The Credit and Balances Overview, and 7: Inventory Stock Levels). Between the fourth and fifth blogs, we released the Alpha II 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 utilizes the Microsoft Adventure Works 2000 database. You can download this database if you want to experiment with SQL without converting one of the DataFlex Embedded Databases. Note that there are multiple versions of the Adventure Works database, and we chose this (somewhat old) version to ensure compatibility with older versions of Microsoft SQL Server. We also selected this database for the reports because we wanted to create reports with images from the database, but the pictures available are too poor to display. For a picture report, we created reports using the Wines Example database, which I will discuss in a later blog. Additionally, we used a script available online to increase all dates in the database, making it appear as though the orders were placed more recently. This is important for this blog's report, as not all employees of the company are over 65 years old!

While the previous two blogs used the same report for two web views, this Sick Leave reporting defines two distinct reports about the company's employees.

The Sick Leave per Age report has its page header section duplicated - once with a column header box and once without. The sections are suppressed by a parameter named SuppressDetails. When suppress details are enabled, the report results are condensed into one page containing summary information; for each employee age range (in 10-year increments), the number of sick days is displayed. Another parameter can be set to indicate whether uneven row coloring for the detail section should be used.

The Sick Leave Overview report displays all employees along with their number of sick days. Depending on a parameter (there are five parameters that can be set), different page header and detail sections are printed. For example, if you select by department, the report will not print the department name in the details section. Other parameters that can be set include hiding/showing the Male/Female column, hiding/showing the Age column, and whether to use an alert color for employees whose birthdays are within a specified number of days before or after the current date.

Both reports use an image in the page header section, and the path to the image can be set via a parameter. In the upcoming (currently available as an Alpha version) of Visual Report Writer, this type of static image can be replaced with an embedded image. The live demo website operates on version 2.1, which requires a path setting for images or deployment of the report in the same folder as at design time (which is not advisable).

Integration

This blog discusses two report views: one for the Sick Leave Per Age report and another for the Sick Leave Overview report. Both views utilize different methods to set the selection criteria and parameters.

Sick Leave Per Age

In the report view, you can browse for a department record. By selecting a department, the data is filtered for that department, meaning the AddFilter message will be used. In the OnInitializeReport, this is accomplished as follows:

WebGet psValue of oDepartmentIDForm to iDepartmentID
If (iDepartmentID <> 0) Begin
    Send AddFilter C_USEMAINVRWREPORTID '{Employee.DepartmentID}' C_VRWEqual iDepartmentID
End

Additionally, a filter can be set for employee titles, which can be added or used separately. A dropdown (cWebCombo) allows you to select the title. The dropdown values are retrieved from the database using an embedded SQL statement that performs a distinct select to obtain that data. The code for this is:

Object oEmployeeTitle is a cWebCombo
    Set piColumnSpan to 7
    Set psLabel to "Title:"
    Set peLabelAlign to alignRight
    Procedure OnFill
        Handle hoSQL hoConnection hoStatement
        Integer iFetchResult
        String sTitle
        // Make it possible to NOT select a title
        Send AddComboItem '' ''
        Get Create (RefClass (cSQLHandleManager)) to hoSQL
        Get SQLFileConnect of hoSQL SQLEmployee.File_Number to hoConnection
        Get SQLOpen of hoConnection to hoStatement
        Send SQLExecDirect of hoStatement "Select Distinct [Employee].[Title] From [Employee]"
        Repeat
            Get SQLFetch of hoStatement to iFetchResult
            If (iFetchResult <> 0) Begin
                Get SQLColumnValue of hoStatement 1 to sTitle
                Send AddComboItem sTitle sTitle
            End
        Until (iFetchResult = 0)
        // Clean up
        Send SQLClose to hoStatement
        Send SQLDisconnect to hoConnection
        Send Destroy of hoSQL
    End_Procedure
End_Object

The filter for this column is conditionally added like the department ID.

WebGet psValue of oEmployeeTitle to sEmployeeTitle
If (sEmployeeTitle <> '') Begin
    Send AddFilter C_USEMAINVRWREPORTID '{Employee.Title}' C_VRWEqual sEmployeeTitle
End

Two checkboxes are used to set the SuppressDetails and UseUnevenRowColoring parameters.

Get GetChecked of oSuppressDetails to bShowDetails
Get ParameterIdByName C_USEMAINVRWREPORTID 'SuppressDetails' to iParameter
Set psParameterValue C_USEMAINVRWREPORTID iParameter to bShowDetails
Get GetChecked of oUseUnevenRowColoring to bUseUnEvenRowColoring
Get ParameterIdByName C_USEMAINVRWREPORTID 'UseUnevenRowColoring' to iParameter
Set psParameterValue C_USEMAINVRWREPORTID iParameter to bUseUnEvenRowColoring

The last option for a user in this report view is to select the report ordering. The user can choose between the ordering already defined in the report (the design-time ordering), sorting by employee last name, or sorting by Sick Leave Hours. Both can be combined with a choice for descending. The combo form is statically filled:

Object oSortOnCombo is a cWebCombo
    Set psLabel to "Sort on:"
    Set peLabelAlign to alignRight
    Set piColumnSpan to 3
    Set pbServerOnChange to True

    Procedure OnChange
        Integer iChoice

        WebGet psValue to iChoice
        WebSet pbEnabled of oSortDescending to (iChoice <> 0)
    End_Procedure

    Procedure OnFill
        Send AddComboItem '0' 'Report sorting'
        Send AddComboItem '1' 'Sort on LastName'
        Send AddComboItem '2' 'Sort on Sick Leave Hours'
    End_Procedure
End_Object

Object oSortDescending is a cWebCheckbox
    Set psCaption to 'Descending'
    Set piColumnIndex to 3
    Set pbEnabled to False
End_Object

The user's choice is read inside the OnInitializeReport method, and if it is not option 0 (sorting as defined at design time), the current report sort order will be removed and newly constructed with the following code:

WebGet psValue of oSortOnCombo to sSortOnValue
Case Begin
    Case (sSortOnValue = '1')
        Send RemoveAllRecordSortFields C_USEMAINVRWREPORTID
        Get GetChecked of oSortDescending to bSortDescending
        If (bSortDescending) Begin
            Send AddRecordSortField C_USEMAINVRWREPORTID '{Employee.Lastname}' C_VRWDescending
        End
        Else Begin
            Send AddRecordSortField C_USEMAINVRWREPORTID '{Employee.Lastname}' C_VRWAscending
        End
        Case Break
    Case (sSortOnValue = '2')
        Send RemoveAllRecordSortFields C_USEMAINVRWREPORTID
        Get GetChecked of oSortDescending to bSortDescending
        If (bSortDescending) Begin
            Send AddRecordSortField C_USEMAINVRWREPORTID '{Employee.SickLeaveHours}' C_VRWDescending
        End
        Else Begin
            Send AddRecordSortField C_USEMAINVRWREPORTID '{Employee.SickLeaveHours}' C_VRWAscending
        End
        Case Break
Case End

The output of this report is not sent to a cWebIFrame control as before, but to a specialized control for Visual Report Writer. This control - available from version 3.0 - can display the report results generated as HTML or as images. The class is called cWebVrwReportViewer, and most of the time, you only need to tell the object where to find the cVRWReport object.

Object oWebViewerPanel is a cWebPanel
    Set pbFillHeight to True
    Object oViewer is a cWebVrwReportViewer
        Set phoReport to oReport
    End_Object
End_Object

To start the report output generation, you send ShowReport to the oViewer report. If the cWebVrwReportViewer object is used to display HTML output, it sends a request to the cVRWReport object to construct the HTML string and return it. This request is named GenerateReportHTML and needs to return HTML in chunks. The chunks can be of any size as long as they are smaller than the Visual DataFlex argument size (see Set/Get_Argument_Size command in the Visual DataFlex help). Knowing this, the GenerateReportHTML is coded as follows:

Function GenerateReportHTML Returns String[]
    String sReportId
    String[] sData
    Integer iArgSize
    Get OpenReport to sReportId
    If (sReportId <> "") Begin
        Get_Argument_Size to iArgSize
        Get ComReportHTMLPreview sReportId (iArgSize - 10) to sData
        Send CloseReport sReportId
    End
    Function_Return sData
End_Function

Sick Leave Overview

The second report view in the live demo application allows you to select values via a ModalDialog. This approach makes the user interface smoother by hiding the controls for selections and parameters until the user wants to change or set them. However, it requires more work from the application developer to create. First, a modal dialog needs to be constructed, and more importantly, the selection criteria need to be made as synchronizable web properties.

The selections dialog is invoked from a cWebMenuItem, and each of the settings in the dialog is created as a property inside the cWebMenuItem object.

Object oSelectionsButton is a cWebMenuItem
    Set psCaption to "Selections"
    Set psCSSClass to "VRWSelectionsButton"
    { WebProperty = True DesignTime = False }
    Property Integer piSelectOn
    { WebProperty = True DesignTime = False }
    Property String psSelectionValueForDepartment
    { WebProperty = True DesignTime = False }
    Property String psSelectionValueForTitle
    { WebProperty = True DesignTime = False }
    Property String psMaritalStatus
    { WebProperty = True DesignTime = False }
    Property Integer piBirthdayRange
    { WebProperty = True DesignTime = False }
    Property String psHideAge
    { WebProperty = True DesignTime = False }
    Property String psHideMF
End_Object

The psCSSClass property allows specifying a different icon for the button based on the selected theme. Note that the live demo website does not allow theme changes, but the Visual Report Writer CSS settings do.

In the OnClick event of the oSelectionsButton object, the values from the above web properties are sent to the modal dialog and displayed. The code for that is:

Procedure OnClick
    tSickLeaveSelections SelectionValues
    WebGet piSelectOn to SelectionValues.iSelectOn
    WebGet psSelectionValueForDepartment to SelectionValues.sDeparmentSelectionValue
    WebGet psSelectionValueForTitle to SelectionValues.sTitleSelectionValue
    WebGet psMaritalStatus to SelectionValues.sMaritalStatus
    WebGet piBirthdayRange to SelectionValues.iBirthDayRange
    WebGet psHideAge to SelectionValues.sHideAge
    WebGet psHideMF to SelectionValues.sHideMF
    Send OpenSelectionsDialog of oSickLeaveOverviewSelectionsDialog Self SelectionValues
End_Procedure

The Modal Dialog (cWebModalDialog) takes the values of the passed struct and sets the values of the controls.

Procedure OpenSelectionsDialog Handle hoReturn tSickLeaveSelections SelectionValues
    WebSet psValue of oSelectOnCombo to SelectionValues.iSelectOn
    WebSet psValue of oSelectionValueForDepartment to SelectionValues.sDeparmentSelectionValue
    WebSet psValue of oSelectionValueForTitle to SelectionValues.sTitleSelectionValue
    WebSet psValue of oMaritalStatus to SelectionValues.sMaritalStatus
    WebSet piSliderValue of oEmployeeBirthDayRange to SelectionValues.iBirthDayRange
    WebSet psValue of oHideAge to SelectionValues.sHideAge
    WebSet psValue of oHideMF to SelectionValues.sHideMF

    Send OnChange of oSelectOnCombo SelectionValues.iSelectOn ''

    Send Popup hoReturn
End_Procedure

If you experiment with the selections dialog (I recommend doing this if you haven't), you will notice that the dialog contents change based on the chosen value in the first combo control. Depending on the current choice, a control for department or title selection is rendered.

Object oSelectOnCombo is a cWebCombo
    Set psLabel to "Select on:"
    Set peLabelAlign to alignRight
    Set piColumnSpan to 6
    Set pbServerOnChange to True

    Procedure OnFill
        Send AddComboItem '0' 'None'
        Send AddComboItem '1' 'Department'
        Send AddComboItem '2' 'Title'
    End_Procedure

    Procedure OnChange String sNewValue String sOldValue
        Case Begin
            Case (sNewValue = '0')
                WebSet pbRender of oSelectionValueForDepartment to False
                WebSet pbRender of oSelectionValueForTitle to False
                Case Break
            Case (sNewValue = '1')
                WebSet pbRender of oSelectionValueForDepartment to True
                WebSet pbRender of oSelectionValueForTitle to False
                Case Break
            Case (sNewValue = '2')
                WebSet pbRender of oSelectionValueForDepartment to False
                WebSet pbRender of oSelectionValueForTitle to True
                Case Break
        Case End
    End_Procedure
End_Object

The oSelectionValueForTitle cWebCombo is coded identically to the one used for the Sick Leave Per Age report integration. The values are retrieved via an embedded SQL statement.

In contrast, the department selection value in the Sick Leave Per Age report integration is managed via a standard cWebForm object, while we use a cWebCombo in this modal dialog. This approach avoids calling a modal dialog from another modal dialog and demonstrates the effective use of embedded SQL in a web application.

Object oSelectionValueForDepartment is a cWebCombo
    Set psLabel to "Department:"
    Set peLabelAlign to alignRight
    Set piColumnSpan to 10

    Procedure OnFill
        Handle hoSQL hoConnection hoStatement
        Integer iFetchResult
        String sId sName

        Get Create (RefClass (cSQLHandleManager)) to hoSQL
        Get SQLFileConnect of hoSQL SQLDepartment.File_Number to hoConnection
        Get SQLOpen of hoConnection to hoStatement
        Send SQLExecDirect of hoStatement "Select [Department].[DepartmentId], [Department].[Name] From [Department]"
        Repeat
            Get SQLFetch of hoStatement to iFetchResult
            If (iFetchResult <> 0) Begin
                Get SQLColumnValue of hoStatement 1 to sId
                Get SQLColumnValue of hoStatement 2 to sName
                Send AddComboItem sId sName
            End
        Until (iFetchResult = 0)
        // Clean up
        Send SQLClose to hoStatement
        Send SQLDisconnect to hoConnection
        Send Destroy of hoSQL
    End_Procedure
End_Object

The above code is similar to that used to fill the cWebCombo for title selection, except that there is no DISTINCT clause in use, and the code and display values differ.

For the oMaritalStatus control, we again use a cWebCombo object that fills itself via an embedded SQL statement using a DISTINCT operation.

Send SQLExecDirect of hoStatement "Select Distinct [Employee].[MaritalStatus] From [Employee]"

While it could have been hard-coded, this approach allows for flexibility should the values change (which they likely won't), eliminating the need for code changes.

The two checkboxes for hiding Age and M/F data are simple cWebCheckbox objects with hard-coded settings for true and false.

Object oHideAge is a cWebCheckbox
    Set psLabel to "Hide Age:"
    Set peLabelAlign to alignRight
    Set psChecked to '1'
    Set psUnchecked to '0'
    Set piColumnSpan to 2
    Set pbShowLabel to True
End_Object

Object oHideMF is a cWebCheckbox
    Set psLabel to "Hide M/F:"
    Set peLabelAlign to alignRight
    Set psChecked to '1'
    Set psUnchecked to '0'
    Set piColumnSpan to 2
    Set pbShowLabel to True
End_Object

The last control in the modal dialog is a cWebSlider object, allowing the user to select a value between 0 and 31 days for an upcoming birthday alert.

When the user clicks one of the two buttons in the modal dialog, the control is passed back to the cWebMenuItem, sending an OnCloseModalDialog event. In this event, the currently selected values are retrieved and stored back in the synchronizable web properties defined above. It felt appropriate to directly generate the report after the modal dialog is closed by clicking the OK button.

Procedure OnCloseModalDialog Handle hoModalDialog
    tSickLeaveSelections SelectionValues
    Boolean bCancel

    Get SickLeaveSelectedValues of hoModalDialog (&SelectionValues) to bCancel
    If (not (bCancel)) Begin
        WebSet piSelectOn to SelectionValues.iSelectOn
        WebSet psSelectionValueForDepartment to SelectionValues.sDeparmentSelectionValue
        WebSet psSelectionValueForTitle to SelectionValues.sTitleSelectionValue
        WebSet psMaritalStatus to SelectionValues.sMaritalStatus
        WebSet piBirthdayRange to SelectionValues.iBirthDayRange
        WebSet psHideAge to SelectionValues.sHideAge
        WebSet psHideMF to SelectionValues.sHideMF
        Send ShowReport of oViewer
    End
End_Procedure

The selected values are retrieved by the cWebReport object in the OnInitializeReport event.

Procedure OnInitializeReport
    String sReportLocation sSelectValue
    Integer iParameter iSelectOn

    Get psReportLocation to sReportLocation

    Get ParameterIdByName C_USEMAINVRWREPORTID 'ImagePath' to iParameter
    Set psParameterValue C_USEMAINVRWREPORTID iParameter to sReportLocation

    Send RemoveAllFilters C_USEMAINVRWREPORTID

    WebGet piSelectOn of oSelectionsButton to iSelectOn
    Get ParameterIdByName C_USEMAINVRWREPORTID 'SelectOn' to iParameter
    Set psParameterValue C_USEMAINVRWREPORTID iParameter to iSelectOn
    Case Begin
        Case (iSelectOn = 0)
            Case Break
        Case (iSelectOn = 1)
            WebGet psSelectionValueForDepartment of oSelectionsButton to sSelectValue
            Send AddFilter C_USEMAINVRWREPORTID '{Employee.DepartmentID}' C_VRWEqual sSelectValue
            Case Break
        Case (iSelectOn = 2)
            WebGet psSelectionValueForTitle of oSelectionsButton to sSelectValue
            Send AddFilter C_USEMAINVRWREPORTID '{Employee.Title}' C_VRWEqual sSelectValue
            Case Break
    Case End

    WebGet psMaritalStatus of oSelectionsButton to sSelectValue
    If (sSelectValue <> '') Begin
        Send AddFilter C_USEMAINVRWREPORTID '{Employee.MaritalStatus}' C_VRWEqual sSelectValue
    End

    WebGet piBirthdayRange of oSelectionsButton to sSelectValue
    Get ParameterIdByName C_USEMAINVRWREPORTID 'BirthdayRange' to iParameter
    Set psParameterValue C_USEMAINVRWREPORTID iParameter to sSelectValue

    WebGet psHideAge of oSelectionsButton to sSelectValue
    Get ParameterIdByName C_USEMAINVRWREPORTID 'HideAge' to iParameter
    Set psParameterValue C_USEMAINVRWREPORTID iParameter to sSelectValue

    WebGet psHideMF of oSelectionsButton to sSelectValue
    Get ParameterIdByName C_USEMAINVRWREPORTID 'HideMF' to iParameter
    Set psParameterValue C_USEMAINVRWREPORTID iParameter to sSelectValue
End_Procedure

The viewer object has one more feature available for us: the ability to respond to a hyperlink click. This event is called OnClickActionLink, which retrieves the value from the HTML anchor element. With this value, we can open a dialog to show (or even modify) the data of the selected row, making Visual Report Writer truly interactive.

Object oViewer is a cWebVrwReportViewer
    Set phoReport to oReport
    Set pbServerOnClickActionLink to True

    Procedure OnClickActionLink String sText
        Send ShowEmployee of oSQLEmployeeModalDialog Self sText
    End_Procedure
End_Object

This feature will not be available until the release of version 3.0 enterprise. I will discuss the coding in a later blog.

This concludes blog number eight about Visual Report Writer web integration. I hope you enjoyed reading this, feel inspired to get started yourself, and look forward to future blogs that will follow soon.