Creating Interactive Dashboards using JavaScript

ALERT: The workarounds presented in this article are deprecated as of version 5.6 with the release of the Visualize.JS framework.  For examples that replacate this functionality using Visualize.JS, check out Mariano Luna's samples on GitHub (available at https://github.com/marianol/JasperEmbedSample

The following has been verified using JasperReports Server from 5.1 to 5.5.  The code samples shown in this article were originally developed by Guillaume Autier.

Normally, using hyperlinks on a dashboard to drill to more detailed content requires opening the detail report in a new browser tab.  There are two known methods to allow content on a dashboard to interact with other content on the same dashboard without requiring a new tab to open. 

The first method leverages the Custom URL feature by adding a web page that uses Web 2.0 technology to the server that calls reports and nested dashboards using the HTTP API.  The main advantage to this approach is that it requires no customization on the server.  The main disadvantage is that it doesn't leverage the simple drag-and-drop dashboard designer interface and forces the user to write Web 2.0 code.

The second method preserves the drag-and-drop functionality of the dashboard designer by making a simple modification to the <jasperserver-pro>/scripts/dashboard.runtime.js file.  The main disadvantage to this approach is that it requires a tool like Firebug to identify the dynamic iFrame IDs used on the dashboard, which makes modifications of the dashboard somewhat cumbersome.

The next sections provide examples that use both methods.

Method 1 Sample: Google Map Dashboard

Usage Scenario: A sales manager for the western region of the United States is in the field using his mobile tablet to keep track of his/her sales offices.  The sales office locations appear in a Google Map on the left side of the dashboard, while the right side of the dashboard contains a nested dashboard showing the aggregated sales metrics for all offices.  Clicking a sales office location on the map causes the dashboard on the right side to be replaced by a report showing the sales data for the selected office.

Figure 1: Dashboard containing map and nested dashboards with aggregate data

Figure 2: Dashboard showing report for single sales office


This dashboard uses a JSP page to provide an interactive dashboard that's useful for scenarios like the one described above.  This JSP page uses the JasperReports Server HTTP API to retrieve content from the repository.  Thus, non-Java based Web scripting frameworks like Ruby on Rails or .NET Web Forms could also be used to implement this dashboard.

IIn this particular sample, the JSP page makes direct calls to the Google Maps API and populates the map with hard-coded latitude-longitude locations for the sales offices.  There are newer versions of this sample that use a JRXML template with a Google Map component which uses the JavaScript enhancement described in Method 2 to interact with the nested dashboard and drill-down report.  The JSP page and repository export for this sample is available here.  Perform the following steps to install the sample:

  • If necessary, install the sample databases and reports
  • Using a SQL client, import the foodmart-gis.sql file to populate the latitude/longitude locations for the sales offices
  • As superuser, import the GIS_v0.5-<database>.zip file
  • If necessary, edit the gmaps_gis.jsp file to reflect your environment.
  • Copy the gmaps_gis.jsp file to the <jasperserver-pro> folder
  • Delete the existing GIS V2 dashboard in the repository and create a new one.  In the dashboard designer, drag a Custom URL to the dashboard canvas and set the URL property to http://<hostname>:<port>/jasperserver-pro/gmaps_gis.jsp?

The way this sample works is as follows:

  • In the gmaps_gis.jsp file, an API call is made to create a Google Map object
  • In the repository in the /Reports/GIS folder, there's a dummy report unit called Store GIS Locations.  The purpose of this report is to hold a Single Select Input Control that we can invoke directly using a web service call to get a list of cities in the stores table
  • The initialize() function of the JavaScript code in gmaps_gis.jsp handles population of the store locations in the Google Map object.  It also populates the frame on the right side with the nested dashboard using the JasperReports Server HTTP API.
  • The replaceURL() function of the JavaScript code in gmaps_gis.jsp handles replacing the dashboard with the store report on the right side of the dashboard when the store location is clicked in the Google Map.

Method 2 Modification

In order to use the sample described in the next section, we need to make the following modification to the <jasperserver-pro>/scripts/dashboard.runtime.js file.  Add a comma to the last function called hasResetButton(), like this:

_hasResetButton : function(){
    return $("button_reset");
},

Then on the second to last line of the file right before the last closing brace, add the following code:


_replaceFrameURL : function(TargetReport,TargetParameterString,TargetFrameNumber) {
    if (TargetReport.startsWith('http')) {
        if (TargetParameterString=='') {
            var url = TargetReport;
        }
        else {
            var url = TargetReport+"&"+TargetParameterString;
        }
    }
    else {
        if (TargetParameterString=='') {
            var url = "/jasperserver-pro/flow.html?_flowId=viewReportFlow&viewAsDashboardFrame=true&reportUnit="
                    + TargetReport
                    + "&fid=contentFrame_frame_"
                    + TargetFrameNumber;
        }
        else {
            var url = "/jasperserver-pro/flow.html?_flowId=viewReportFlow&viewAsDashboardFrame=true&reportUnit="
                    + TargetReport
                    + "&fid=contentFrame_frame_"
                    + TargetFrameNumber
                    + "&"
                    + TargetParameterString;
        }
    }
 
    var FrameBaseName = this.CONTENT_FRAME_PREFIX + 'frame_' + TargetFrameNumber;
    var Frame = document.getElementById(FrameBaseName);
    Frame.src = url;
},
 
    // This is a loop to call many reports in differents frames in a single call   
    //      The call is made like this :
    //      replaceManyFrames('/reports/report1@@/reports/report2','Country=France@@StoreID=4','2@@3')
    //      The inputs have to be consitent
    //
    replaceManyFrames : function(TargetReports,TargetParameterStrings,TargetFrames) {
        if (TargetReports.indexOf('@@')>-1) {
            var XTargetReport = "";
            var XTargetParameterString ="";
            var XFrame = "";
            var valueArrayReports = TargetReports.split('@@');
            var valueArrayParameters = TargetParameterStrings.split('@@');
            var valueArrayTFrames = TargetFrames.split('@@');
 
            for (var i = 0; i < valueArrayReports.length; i++) {
                // decode each and send to replaceFrameURL
                XTargetReport = valueArrayReports[i];
                XTargetParameterString =valueArrayParameters[i];
                XFrame = valueArrayTFrames[i];
                this._replaceFrameURL(XTargetReport,XTargetParameterString,XFrame);
            }
        }
    }

No restart of the server is necessary for these changes to take effect.

Method 2 Sample: Drill Across Dashboard

The sample reports and dashboard are available here.

The drill across dashboard consists of 3 reports: Order by Country, Order by State/Province for Country, and Order Details. When the dashboard first loads, all three reports contain data for all countries. Clicking on a country in the Orders by Country report causes the other two reports to reload with data for the selected country. The Order Details report can be filtered further by clicking on a State or Province in the Orders by State/Provice for Country report.

Figure 3: Dashboard in initial state

Figure 4: Dashboard with country "USA" selected in Orders by Country

Figure 5: Dashboard with country "USA" and state "Idaho" selected

NOTE: Creating hyperlinks on chart components is beyond the scope of this article. For information on how to create value hyperlinks on Chart and Chart Pro components, refer to sections 5.19 and 12.3 of the iReport Ultimate Guide and page 181 of the JasperReports Ultimate Guide. For information on how to create value hyperlinks on HTML 5 chart components, click here.

The drill across functionality relies on using hyperlinks of type Reference with the following URL format:

javascript:localContext.replaceManyFrames('<report 1 repo path>@@<report 2 repo path>@@...','<param 1 name>=<report object 1>&<param 2 name>=<report object 2>@@<param 1 name>=<report object 1>&<param 2 name>=<report object 2>@@...','<iframe id 1>@@<iframe id 2>@@...')

Notice that the @@ symbol acts as the delimiter between multiple frame targets. Also note that you can have more than one parameter name value pair in the second replaceManyFrames function parameter separated by an ampersand (&), but the name value pairs separated by the @@ delimiter correspond with each targeted report. If you intend to only target one frame, you will still need the @@ delimeter after the first report repo path in the first replaceManyFrames function parameter.

In order to get the iFrame IDs specified in the third replaceManyFrames function parameter, you will need to use a tool like Firebug to identify them. After the reports and/or web content are added to the dashboard and Firebug is installed and enabled, on the HTML tab, enter the search term "contentFrame_frame_". The iFrame ID will appear immediately to the right of the found instances, as shown in Figure 6 below.

Figure 6: Using Firebug to identify iFrame IDs

NOTE: the iFrame IDs are dynamically generated when an element is added to a dashboard. Deleting a report and then re-adding it will cause the iFrame iD counter to increment. Drilling will not work until the hyperlink on the JRXML template is updated to reflect the change.

Feedback
randomness