CHAPTER 10

Reporting

In most projects, reporting is something that is overlooked until the last minute. Everyone is focused on getting information into the system and making the views look and feel right. Then someone starts using the application and says, "Wouldn't it be good if users could print out a copy of the information to take with them and refer to throughout their day?" "Oops, we didn't think about that." (Insert chirping crickets here.)

It makes sense that users would want to take their to-do information with them. But you're in a hurry and want to get this application rolled out quickly. What do you do?

In this chapter, you will learn how to create a reporting facility using the Collab-Todo domain and a popular open source reporting engine, JasperReports.1 Creating a reporting facility will give you a slightly different view of Grails. You will use dynamic invocation to retrieve data for the report and pass the data to JasperReports. Along the way, you will see how easy it is to use third-party libraries in a Grails application.

The Report

The goal of this chapter is to allow users to run a report that contains their list of to-do items. Figure 10-1 shows an example of the report you will create and integrate into the Collab-Todo application.

__________


image

Figure 10-1. PDF of the to-do report

This simple PDF report displays a user's to-do items.

Overview of the Reporting Function

Now that you know what the desired result is, how do you get there? Start by adding some technical constraints to help frame the solution:

  • You want to be able to create multiple reports.
  • You want the report to be available in multiple formats, including PDF, HTML, TXT, RTF, XLS, CSV, and XML.
  • You believe in the DRY (principle and want to maintain a separation of concerns and encapsulation.
  • You want to leverage the Grails domain model and dynamic methods to retrieve the report data.

Taking these technical constraints into consideration, you can construct the solution illustrated in Figure 10-2.

image

Figure 10-2. Reporting facility overview

The first component in the top-left part of Figure 10-2 represents an HTML page. The HTML page needs to provide a way for users to indicate that they want to generate a report. You accomplish this by creating a report tag. You could just hard-code all the logic required to invoke the next step in the process, but knowing that you will have multiple reports, that would be a lot of copying, pasting, and tweaking. It wouldn't be very user friendly to the HTML developer either. By creating a tag, you're able to encapsulate all of the invocation knowledge.

Just like most links in Grails, a tag invokes an action on a controller. If you consider the technical constraints, the controller should have very little knowledge about the actual report to be created. It should control (pun intended) the execution of the reporting process. In this case, it calls the TodoController to gather the data for the report.

The TodoController uses dynamic finder methods on the Todo class to retrieve the report data from the database and returns the result to the ReportController. Now that the ReportController has data for the report, it calls the ReportService to create the report.

The ReportService prepares the data, locates the appropriate report template, and invokes the report engine, JasperReports. The report engine merges the data and the template to create the report. It returns the report to the ReportService, which in turn returns the report to the ReportController. The ReportController then wraps the report in the appropriate headers and returns it to the browser to be displayed.

This is just an overview. Lots of questions are probably going through your head right now. The rest of the chapter will explore each step in more detail and will hopefully answer most, if not all, of your questions.

Now that you have an idea of how to approach creating the reporting facility, you need to add JasperReports to the application and create the report template. The next couple of sections will take you through that process. With that accomplished, you will construct the individual components and tie everything together. By the end of this chapter, you will have a pretty nice reporting facility that will allow you to easily create reports in multiple output formats.

Reporting Tools

In this section, you will receive a high-level overview of JasperReports (the runtime reporting engine) and iReports (the report designer). You will install iReports and add the appropriate JasperReports libraries to the application.

Overview

JasperReports is a popular open source Java reporting engine from JasperSoft.2 You can use JasperReports to define robust, professional reports that include graphics and charts.You set up and define JasperReports reports using XML. The reporting engine uses the report definition and a data source to produce a report in a variety of output formats, including PDF, XML, HTML, CSV, XLS, RTF, and TXT.

__________

JasperReports uses third-party libraries to render reports. The engine itself is not an executable application. It is intended to be embedded into a client or server-side application. The application is responsible for passing the XML report definition, data source, parameters, and configuration information to a report exporter. The exporter returns a ByteArrayOutputStream containing the report content to the application. In the case of a typical server application, the application sets the appropriate content type on an HTML response and streams the results to the browser.


Tip JasperReports relies on Abstract Window Toolkit (AWT) to render the report. If you intend to run Collab-Todo in a Linux/Unix environment without graphics support, you will need to specify the headless environment by setting -Djava.awt.headless=true. You can do this by setting the JAVA_OPT environmental variable (e.g., JAVA_OPT='-Djava.awt.headless=true').


The XML report definition, known as a report template in Jasper terms, defines the content and layout of the report. You can define the report by hand using an XML editor, but this would be time consuming and error prone. Luckily, JasperSoft created iReports, a graphic report designer for JasperReports that defines and compiles JasperReports. It is much easier to build reports using iReports than it is to build the XML report definition by hand.


Note A full exploration of JasperReports and iReports is beyond the scope of this book. In addition to the JasperSoft web site, the Apress books The Definitive Guide to JapserReports3 and The Definitive Guide to iReports4 are good sources of information.


Installing JasperReports and iReports

Installing JasperReports and iReports is easy. You can download iReports from the iReports home page5 or from SourceForge.6 Download and execute the Windows installer version.

__________

3. Teodor Danciu and Lucian Chirita, The Definitive Guide to JasperReports (Berkeley, CA: Apress, 2007).

4. Giulio Toffoli, The Definitive Guide to iReports (Berkeley, CA: Apress, 2007).


Note If you're using an operating system other than Windows, you will need to download the .zip or .tar file and install it manually by unzipping or unpacking it into an appropriate location.


The Windows installer installs iReports in the directory of your choice; remember where it's installed. iReports includes JasperReports and all of the required third-party libraries. Copy the following files from the iReports/lib directory to the collab-todo/lib directory:

  • poi-x.x.x-FINAL-x.jar
  • commons-beanutils-x.x.jar
  • commons-collections-x.x.jar
  • commons-dbcp-x.x.x.jar
  • commons-digester-x.x.jar
  • commons-logging-x.x.x.jar
  • commons-logging-api-x.x.x.jar
  • commons-pool-x.x.jar
  • itext-x.x.x.jar
  • jasperreports-x.x.x.jar

Grails uses these JAR files to invoke JasperReports.

Creating the To-Do Report

Now that iReports is installed, you're ready to build the to-do report, as shown in Figure 10-1. You will take the following steps to create the report:

  1. Define a JavaBeans data source for Collab-Todo.
  2. Create the first iteration of the report using the iReport Wizard.
  3. Enhance the report.
  4. Compile the report.

Defining the Data Source

If you recall from the overview, the ReportController gathers the report data from another controller and ultimately passes the data to the reporting engine. This means that instead of using a JDBC database connection, the report uses a JavaBeans data source. Let's define the data source:

  1. From iReports, select the Data image Connections/Data Sources menu option. You will be presented with a list of currently defined and example data sources.
  2. Click the New button to define a new data source. You will be presented with a "Connections properties" dialog box.
  3. Select the "JavaBeans set data source" option from the list of available data sources, and click the Next button. You will be presented with the "JavaBeans set data source" options.
  4. Set the name to Collab-Todo and blank out the factory class and static method fields. Figure 10-3 shows the contents of this page.

    image

    Figure 10-3. The "JavaBeans set data source" page

  5. Click the Save button. You should see your new data source in the list of available data sources.
  6. Close this window.

You're now ready to define the report using the wizard.

Using iReports

Now that you've defined a data source, you're ready to start building the report. If you're already familiar with iReports, you can skim through this section and move on to "The Report Tag" section.

If you're familiar with iReports, you can go ahead and create the report however you see fit. However, we're assuming that you're new to iReports, so follow these steps to use the iReports Wizard.

  1. Select the File image Report Wizard menu option.
  2. Specify the data that you want in the report. For your purposes, you want to set the Connections/Data Sources field to Collab-Todo, if it isn't already. This tells iReports that you're using a JavaBeans data source.
  3. Tell iReports the domain class that contains the data. You're creating a to-do report, so the "JavaBean class" field should be set to Todo. The wizard should look something like Figure 10-4.

    image

    Figure 10-4. Specifying the data source

  4. Assuming that you followed the instructions in the "Making iReports Aware of the Collab-Todo Classes" sidebar, when you click the Next button, you will be given the opportunity to select the fields that should be included in the report. The wizard should look something like Figure 10-5.

    image

    Figure 10-5. Selecting fields

    Add all of the fields except for the class and metaClass, and click the Next button. The metaClass is internal Grails metadata and wouldn't be appropriate for a report. The next screen lists the fields and their datatypes. Click the Next button to continue.

  5. You should now be on step 3, "Group by." You don't have any work to do here, so click the Next button to move to step 4, Layout.
  6. The wizard lets you pick from some predefined layouts. As a starting point, check "Columnar layout" and highlight classicC.xml. Your screen should look something like Figure 10-6.

    image

    Figure 10-6. Selecting the layout

  7. Click the Next button to move to step 5, Finish. Click the Finish button to generate the report template. You should have a report template that looks like Figure 10-7.

    image

    Figure 10-7. The to-do report template

Congratulations; you have your first report. It isn't pretty, but it is functional. At this point, you could compile the report and run it. If that's what you would like to do, skip forward to "Compiling the Report." Next, you will make the report a little more usable.

As you can see, iReports used the attribute names to create the labels. As a first step toward making the report a little more usable, type in your own text for the labels. You may have to resize the field by dragging the right side of the label.

Enhancing the Report

As you can see, iReports used the Todo property names to create the labels. You should rearrange the labels and fields to an order that makes more sense. You may want to copy the example shown in Figure 10-8.


image

Figure 10-8. Rearranged labels and fields

The report is starting to look better, but you could do more. Follow these steps to enhance the report:

  1. Take a good look at the Note field. It's a little small, so expand it by dragging the bottom edge of the field down.
  2. The title could use a little work, so add the username, which is passed to the report as a parameter. To define the username, right-click the parameter entry in the Document Structure window and select Add image Parameter. iReports displays a pop-up window that lets you input the parameter name. Set the Parameter Name field to userName, as shown in Figure 10-9.

    image

    Figure 10-9. Setting up the parameter

  3. Now that you have defined the parameter, you can use it in the report header. Insert the userName parameter into the header by dragging it from the list of parameters in the Document Structure window to the report header. Also, change the current text to read "Todo for:." Figure 10-10 shows an example of the new header.

    image

    Figure 10-10. The new header

  4. Save the report as userTodo.jrxml in the Collab-Todo application directory.

You're now ready to compile the report and start integrating it into the application.

Compiling the Report

Now that you've set up the report, it's time to make it available to the application. You need to compile it and copy the compiled file to the appropriate application directory.

Select Build image Compile in iReports. You will see the results of the compilation at the bottom of iReports in the output console. Hopefully, the build is successful the first time, but if it isn't, work through the error messages and try again. Once you have a successful build, copy the userTodo.jasper file from the iReports home directory to the collab-todo/web-app/reports directory.

The Report Tag

You have used several tags, but now it's time to create your own. If you have developed tags before, you will be pleasantly surprised by how easy it is in Grails. You will create a report tag library to hold the report tag and then implement the tag as a closure.

Tag Library Overview

It is a best practice to group tags by topic into a tag library. In this case, all report tags are grouped together in the grails-app/taglib/ReportTagLib.groovy. Notice the Grails conventions again: tag libraries end with TagLib.groovy and are located in the grails-app/taglib directory. The following command illustrates creating the tag library:

> grails create-tag-lib Report

The results of the command can be seen here:


Welcome to Grails 1.0-2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: C:Appsgrailsgrails-1.0-2

Base Directory: C:workspace collab-todo
Environment set to development
Running script C:Appsgrailsgrails-1.0-RC1scriptsCreateTagLib.groovy
     [copy] Copying 1 file to C:workspacecollab-todograils-app aglib
Created TagLib for Report
     [copy] Copying 1 file to C:workspacecollab-todo estintegration
created TaglibTests for Reports

As you have come to expect, Grails created the tag library and an integration test. The next step is to create the tag.

Creating the Tag

As you saw in Chapter 8, a tag is a closure that has two arguments: a map of tag attributes and the body of the tag. Listing 10-1 illustrates the tag library class.

Listing 10-1. Tag Library Class

class ReportTagLib {

    def report = { attrs, body ->

    . . .

    }
}

Now you have to figure out what to put between the curly braces. You know you have to let the users choose in which format they want to receive the report, and you know that you have to invoke the ReportController to generate the report. With this in mind, you can start designing the tag inputs. Let's take a look at it from a usage point of view. Listing 10-2 illustrates how that tag might look inside a view.

Listing 10-2. Report Tag

  <g:report id="todoReport" controller="TodoController"
            action="userTodo" report="userTodo"
            format="PDF,HTML,CSV,XLS,RTF,TXT,XML">
  <input type="hidden" name="userName" value="${todoList[0]?.owner}" />
</g:report>

Based upon the requirements, this should make some sense. The report tag has an id attribute to uniquely identify it. The controller and action attributes work together to specify which action to run to get the report data. The report attribute specifies which report to generate, and the format attribute supplies a list of report formats for users to select. The hidden input determines which user's information to gather. With this information, you can turn your attention back to the tag implementation. The tag body gives you a pretty good hint about the implementation. The tag generates a <form> element that invokes the ReportController's index action. The format attribute is used to display icons representing each of the report formats. Listing 10-3 contains the implementation of the report tag.

Listing 10-3. Report Tag Implementation

01  def report = { attrs, body ->
02
03    validateAttributes(attrs)
04    def appPath = grailsAttributes.getApplicationUri(request)
05
06    out << """
07        <form id="${attrs['id']}" name="${attrs['report']}"
08          action="${appPath}/report">
09        <input type="hidden" name="format"/>
10        <input type="hidden" name="file" value="${attrs['report']}"/>
11       <input type="hidden" name="_controller"
12          value="${attrs['controller']}"/>
13       <input type="hidden" name="_action" value="${attrs['action']}"/>
14   """
15   TreeSet formats = attrs['format'].split(",")
16   formats.each{
17     out << """
18         <a href="#${attrs['report']}Report"
19                    onClick="document.getElementById('${attrs['id']}').
20                     format.value = '${it}';
21                     document.getElementById('${attrs['id']}').submit()">
22         <img width="16px" height="16px" border="0"
23                    src="${appPath}/images/icons/${it}.gif" />
24         </a>
25     """
26   }
27   out << body()
28   out << "</form>"
29 }
30
31 private void validateAttributes(attrs) {
32   //Verify the 'id' attribute
33   if(attrs.id == null)
34     throw new Exception("The 'id' attribute in 'report' tag mustn't be 'null'")
35
36   //Verify the 'format' attribute
37   def availableFormats = ["CSV","HTML","RTF","XLS","PDF","TXT","XML"]
38   attrs.format.toUpperCase().split(",").each{
39     if(!availableFormats.contains(it)){
40       throw new Exception("""Value ${it} is a invalid format attribute.
41             Only ${availableFormats} are permitted""")
42     }
43   }
44 }

Let's take a look at the tag implementation line by line. In line 1, the tag takes two arguments: an attribute map and the body. Because you specified the report formats on the tag, the tag in line 3 has to validate that you specified supported report formats. Line 4 creates a local variable for the application path. Lines 6–14 create the form and hidden input fields to allow tag attributes to be passed to the ReportController. But wait, where was out defined? The out variable is a handle to the output stream that is injected by Grails.

Lines 15–25 iterate over the tag format attribute to create icons for each of the report formats. If you look closely, you will see that when the user selects the report format, the hidden input field format is set and the form is submitted. Line 27 outputs the tag body, and line 28 completes the form definition. Lines 31–44 are the body of the validateAttributes method called on line 3. This method iterates through the tag format attribute to validate that you specified valid report formats.

The ReportController and the ReportService

As you saw in Figure 10-1, the ReportController follows these three steps to create the report:

  1. Gather data for the report by invoking a controller/action.
  2. Locate a compiled report
  3. Ask the ReportService to generate the report, and wrap the output with the appropriate content type.

Start by running the following code to create the ReportController :

> grails create-controller Report

Now you need to implement the three steps listed previously. Listing 10-4 illustrates gathering the report data.

Listing 10-4. Gathering the Report Data

import org.springframework.context.ApplicationContext;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes

class ReportController {
   ReportService reportService

   def index = {
      // Gather data for the report.
      // 1) Find the controller
      ApplicationContext ctx = (ApplicationContext) session.
         getServletContext().
         getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT);
      def controller = ctx.getBean("${params._controller}");

      // 2) Invoke the action
      def inputCollection = controller."${params._action}"(params)
      params.inputCollection = inputCollection

The first step in gathering data for the report is to dynamically invoke the action supplied by the report tag. The tag specifies the controller and the action to be invoked to gather the data. The controller and the action to invoke are passed in the params map. The problem is that the values are just strings. You use the Spring application context to get an instance of the controller. Then you invoke the action on the controller, passing the params map to it.

Next, you need to locate the report. The compiled reports are located in the web-app/reports directory. Listing 10-5 illustrates using the servletContext to locate and load the report.

Listing 10-5. Locating and Loading the Report

// Find the compiled report
def reportFileName = reportService.reportFileName("${params.file}")
def reportFile = servletContext.getResource(reportFileName)
if(reportFile == null){
    throw new FileNotFoundException(""""${reportFileName}" file must be in
        reports repository.""")
}

Finally, you need to generate the report and wrap the output with the proper content type. The ReportController calls the ReportService to generate the report. You could collapse the service into the controller, but the controller's purpose is to control, not do the actual work. The controller should delegate the actual work to some other component. Delegating the actual report generation to the report service maintains a separation of concerns and encapsulates knowledge of the JasperReports libraries into a single location, the ReportService. Listing 10-6 illustrates delegating to the ReportService and wrapping the output in the appropriate context type.

Listing 10-6. Calling the ReportService

// Call the ReportService to invoke the reporting engine
   switch(params.format){
      case "PDF":
         createPdfFile(reportService.generateReport(reportFile,
            reportService.PDF_FORMAT,params ).toByteArray(),params.file)
         break
      case "HTML":
         render(text:reportService.generateReport(reportFile,
            reportService.HTML_FORMAT,params),contentType:"text/html")
         break
      case "CSV":
         render(text:reportService.generateReport(reportFile,
            reportService.CSV_FORMAT,params),contentType:"text")
         break
      case "XLS":
         createXlsFile(reportService.generateReport(reportFile,
            reportService.XLS_FORMAT,params).toByteArray(),params.file)
         break
      case "RTF":
         createRtfFile(reportService.generateReport(reportFile,
            reportService.RTF_FORMAT,params).toByteArray(),params.file)
         break
      case "XML":
         render(text:reportService.generateReport(reportFile,
            reportService.XML_FORMAT,params),contentType:"text")
         break
      case "TXT":
         render(text:reportService.generateReport(reportFile,
            reportService.TEXT_FORMAT,params),contentType:"text")
         break
      default:
         throw new Exception("Invalid format")
         break
   }
}

/**
* Output a PDF response
*/
def createPdfFile = { contentBinary, fileName ->
   response.setHeader("Content-disposition", "attachment; filename=" +
        fileName + ".pdf");
   response.contentType = "application/pdf"
   response.outputStream << contentBinary
}

/**
* Output an Excel response
*/
def createXlsFile = { contentBinary, fileName ->
   response.setHeader("Content-disposition", "attachment; filename=" +
      fileName + ".xls");
   response.contentType = "application/vnd.ms-excel"
   response.outputStream << contentBinary
}

/**
* Output an RTF response
*/
def createRtfFile = { contentBinary, fileName ->
   response.setHeader("Content-disposition", "attachment; filename=" +
        fileName + ".rtf");
   response.contentType = "application/rtf"
   response.outputStream << contentBinary
}

Now that you have the controller, you need to set up the ReportService. You can create the ReportService by running this command:

> grails create-service Report

The main functionality in the ReportService is encapsulation of the logic to generate the report using the JasperReports API. Listing 10-7 contains the ReportService.

Listing 10-7. ReportService

01  import java.io.ByteArrayOutputStream
02  import java.io.InputStream
03  import java.sql.Connection
04  import java.sql.Timestamp
05  import java.util.HashMap
06
07  import net.sf.jasperreports.engine.JRException
08  import net.sf.jasperreports.engine.JRExporter
09  import net.sf.jasperreports.engine.JasperPrint
10 import net.sf.jasperreports.engine.JasperFillManager
11 import net.sf.jasperreports.engine.JRExporterParameter
12 import net.sf.jasperreports.engine.export.JRCsvExporter
13 import net.sf.jasperreports.engine.export.JRHtmlExporter
14 import net.sf.jasperreports.engine.export.JRHtmlExporterParameter
15 import net.sf.jasperreports.engine.export.JRPdfExporter
16 import net.sf.jasperreports.engine.export.JRXlsExporter
17 import net.sf.jasperreports.engine.export.JRXmlExporter
18 import net.sf.jasperreports.engine.export.JRRtfExporter
19 import net.sf.jasperreports.engine.export.JRTextExporter
20 import net.sf.jasperreports.engine.export.JRTextExporterParameter
21 import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
22
23 class ReportService {
24
25   boolean transactional = true
26
27   int PDF_FORMAT = 1;
28   int HTML_FORMAT = 2;
29   int TEXT_FORMAT = 3;
30   int CSV_FORMAT = 4;
31   int XLS_FORMAT = 5;
32   int RTF_FORMAT = 6;
33   int XML_FORMAT = 7;
34
35
36
37   /**
38   * Generate the Report
39   */
40   def generateReport = {jasperFile, format, parameters ->

41
42     // Setup the Data Source
43     JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(
44           parameters.inputCollection);
45
46     InputStream input = jasperFile.openStream()
47     JRExporter exporter
48     ByteArrayOutputStream byteArray = new ByteArrayOutputStream()
49     JasperPrint jasperPrint = JasperFillManager.fillReport(input, parameters,ds)
50     switch (format) {
51       case PDF_FORMAT:
52         exporter = new JRPdfExporter()
53         break
54       case HTML_FORMAT:
55         exporter = new JRHtmlExporter()
56         exporter.setParameter(JRHtmlExporterParameter.
57             IS_USING_IMAGES_TO_ALIGN, false)
58         break
59       case CSV_FORMAT:
60         exporter = new JRCsvExporter()
61         break
62       case TEXT_FORMAT:
63         exporter = new JRTextExporter()
64         exporter.setParameter(JRTextExporterParameter.CHARACTER_WIDTH,
65             new Integer(10));
66         exporter.setParameter(JRTextExporterParameter.CHARACTER_HEIGHT,
67             new Integer(10));
68         break
69       case XLS_FORMAT:
70         exporter = new JRXlsExporter()
71         break
72       case RTF_FORMAT:
73         exporter = new JRRtfExporter()
74         break
75       case XML_FORMAT:
76         exporter = new JRXmlExporter()
77         break
78       default:
79         throw new Exception("Unknown report format")
80     }
81     exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, byteArray)
82     exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint)
83     exporter.exportReport()
84     return byteArray
85   }
86
87     def reportFileName = { reportName ->
88        return "/reports/"+reportName+".jasper"
89     }
90 }

Let's walk through this. Line 40 is the beginning of the generateReport closure. As you can see, generateReport takes three input parameters: the report template, the report format, and parameters. Lines 42–44 define and populate a JavaBeans collection data source. If you look closely, you will see that the data source is populated from an input collection contained with the parameters. This is the collection that the ReportController created.

Lines 46–48 do some additional setup. Line 49 passes the report template, parameters, and JavaBeans data source to the reporting engine. Lines 50–80 set up the appropriate rendering component based upon the report format type requested, while lines 81–82 set some additional parameters on the renderer. Line 83 is where the real magic happens: it causes the report to be generated. The results are returned to the caller on line 84.

In Listing 10-5, you may recall seeing something like the following:

reportService.reportFileName($params.file)

Lines 87–89 contain the implementation of this method, which, as you can tell, is pretty basic. You simply prepend the directory and append the file extension to create the report file name. You could easily have done this in the ReportController, but you really don't want the ReportController to know that you're using JasperReports. By doing it this way, you maintain a separation of concerns and encapsulation.

You're now ready to tie it all together and see the result of your work.

Tying It All Together

You're about to see the result of your work. You installed iReports and copied the appropriate libraries to the application. You created the report and the report tag library. You created the ReportService and the ReportController. The only thing left to do is to write the code that gathers the report data and hook the report tag into the application.

Gathering the Report Data

Recall from "The Report Tag" section that the tag allows you to specify the controller and the action to call to gather the report data. In this case, you'll specify the TodoController and the userTodo action, so you'll need to create a userTodo action on the TodoController. Listing 10-8 contains the content of the action.

Listing 10-8. Gathering the Report Data

   def userTodo = {
         def user = User.get(session.user.id)
         return Todo.findAllByOwner(user)
   }

The code in Listing 10-8 finds all the to-dos for the current user and returns the results. Now, you have to hook the report tag into the application.

Adding the Report Tag to the Application

The last step is to add the report tag to the Todo List view and then edit the Todo List view (grails-app/views/todo/list.gsp). At the bottom of the file, after the paginate logic, add the report tag. Follow Listing 10-9 as an example.

Listing 10-9. Adding the Report Tag

    . . .
    <div class="paginateButtons">
      <g:paginate total="${Todo.count()}" />
    </div>
      <g:report id="todoReport" controller="TodoController"
                action="userTodo" report="userTodo"
                format="PDF,HTML,CSV,XLS,RTF,TXT,XML">
        <input type="hidden" name="userName" value="${todoList[0]?.owner}" />
      </g:report>
    </div>
  </body>
</html>

Now let's take a look at the results. Start the application, log in, and select the PDF icon. Figure 10-11 shows the report.

image

Figure 10-11. The report

Congratulations, you have successfully built a reporting facility. Now that you have one, it would be a shame to not reuse it.

The Report List

You have done a lot of good work; you have constructed all of the core components of a reporting facility and enhanced a view with the report tag to give users access to reports. You also saw how to use the following command to pass report parameters to the data collection action:

<input type="hidden" name="userName" value="${todoList[0]?.owner}" />

In this case, the parameter was a hidden field, but it could have just as easily been a visible input field.

It would be reasonable for users to say that they want to be able to specify a due-date range and to only show to-do items that are within the range. What would it take to fulfill such a request? Well, you would need to construct a Reports List view and hook it into the application. This view would need to list all of the available reports. If a report doesn't require any additional parameters, you could execute it directly from the Reports List view. If a report requires additional parameters, it will launch another page that allows users to specify the input parameters for the report.

The best thing is, you can reuse all of the components you just created. Pretty cool. Given the fact that this is all a rehash of everything you have learned, we are going to leave this as an exercise for you to develop on your own. However, if you get in a jam, check out the application source code for this chapter in the Source Code/Download area of the Apress web site (http://www.apress.com). We have included just such a solution in the application.

An Alternate Approach

Earlier in the chapter, we discussed using the domain model to give data to the reports or using embedded SQL. We decided to use the domain model approach for several good reasons, but the alternative is worth considering.

Marcos Fábio Pereira created a Jasper plug-in for Grails. As a matter of fact, Marcos' work provided some of the inspiration for this chapter. So here is a big shout-out to Marcos: thank you Marcos; your good work is appreciated.

The Jasper plug-in takes the embedded SQL approach. Depending upon your circumstances, this may be a good solution for you. You can take a look at the Jasper plug-in by running:

> grails list-plugins

and locating the Jasper entry. Then run:

> grails install-plugin Jasper 0.5

A look at the Jasper tag will get you started.

Summary

You accomplished a lot in this chapter. Instead of building a static solution for one report, you built a dynamic reporting facility that supports multiple reports and multiple formats, and accepts input to drive the selection of report data.

In the process of building the reporting facility, you installed and configured the JasperReports reporting engine and the iReports report designer. You used iReports to define and compile the report template.

You then moved on to creating a report tag library. You created the ReportController for the report tag to call. The ReportController took care of facilitating the data collection and invoking the ReportService to generate the report. In the ReportService, you wrapped the Todo domain objects in a JavaBeans data source, and you passed the data source, parameters, and report template to the JasperReports reporting engine for processing. The reporting engine returned a binary version of the report, which the ReportController then returned to the user.

This chapter allowed you to learn some new things, and it reinforced some things you learned earlier. It showed you how to build a reporting facility that supports multiple reports and formats. The next chapter leverages the ReportServicel in a nightly batch job to generate and e-mail user to-do reports.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset