Chapter 9. Scripting and Event Handling

We have come a long way since we started this book. We have seen all the different aspects of the BIRT Report Development environment. We have seen how to drag-and-drop reports with the Palette and the Report Designer, how to work with the outline and the navigator, and how to set properties or each of the report components.

We have seen how BIRT can link to data sources through JDBC drivers and how to build dataset through those connections. We have seen how to format reports using properties and styles.

Now we are going to look at the aspect of BIRT that makes it really powerful—scripting. Scripting is a complex topic, but once we understand it, we will be able to make BIRT do some really amazing things such as:

  • Modifying data as it comes through
  • Building data sources off of Java objects
  • Building reports from the ground up

BIRT utilizes the Mozilla Rhino Engine to handle its scripting capabilities. What this means is that inside of BIRT reports, Report designers have full access to all primitive types, object, methods, and libraries accessible to JavaScript. As an added bonus, report developers even have full access to all Java classes that are in the classpath. In the BIRT Report Designer, this means one have full access to all the Java objects in the Eclipse environment. One can even use custom Java objects in his/her report to handle certain aspects of processing.

In the following sections, we are going to look at the two different types of scripting that BIRT has to offer—expressions and event handling. We are going to look at how to access different types of BIRT properties using expressions. We will then look at how to handle report generation events using BIRT's implementation of the Rhino engine and how to handle those same events using Java objects. Although a prior knowledge of Java or JavaScript is not necessary, it will greatly enhance the understanding of the topics covered in this chapter. But to follow along effectively, the reader should understand:

  • Variables
    • Types
    • Creation
    • Assignment
  • Operators
    • Arithmetic
    • Logical
    • Comparison
  • Functions
  • Objects
    • Properties
    • Methods

Types of Scripting

In BIRT, there are two different types of scripting—expressions and event handling. Expressions are simple bits of script, usually ranging from 1 to less than 10 lines of code, returning a single value for use in a BIRT report. Event Handling scripts are usually a bit larger than expressions, and are meant to accomplish some sort of task such as retrieve a row from a dataset, manipulate some data before it is sent to output, or handle preparations for rendering. It is recommended that the reader learn more about JavaScript to assist with their report development.

Expressions

Expressions make up a bulk of the scripting used in BIRT. In fact, we have used Expressions multiple times already in this book. Expressions are usually single line statements that return a single result. Tasks such as retrieving a value from a row and outputing it in a data element, retrieving parameter values, and the Highlight expressions used in the last chapter are all examples of Expressions. Any time we use the Expression editor in BIRT, we are working with Expressions.

Let's take a look at the most commonly used Expression—the Data element. Open up the Customer Orders report that we've used in the last chapter. Here we have a finished report with numerous Expressions used throughout. Let's take a look at what one of these Expressions look like.

Expressions

This preceding screenshot shows us a very simple expression that retrieves the value of the CUSTOMERNUMBER column from the current row. It is taken from the Data element in the Orders detail table. dataSetRow is an array that represents the current row in the detail band. The screenshot illustrates that a single value is returned with a simple line of code.

Let's take a look at another more complicated example. Look at step 14 of the Bar Chart example from last chapter. There, we have an expression that looks like:

(row["ORDERDATE"].getYear() + 1900).toString() + '-' + (row["ORDERDATE"].getMonth() + 1).toString()

This Expression retrieves the ORDERDATE value from the current row using the row array, and in two separate parts—one part will retrieve the year and another will retrieve the month using the standard Javascript functions getYear and getMonth. In BIRT, getYear will return the number of years since the epoch, which is 1900. So, in order to get the actual number of years, we need to add 1900 to the result. Therefore, for the year 2007, the result from getYear will return 107, so adding 1900 will return 2007. The situation arises with getMonth; it will return the month from a zero offset. So, January will return 0, February will return 1, and so on, requiring us to add 1 to get the correct month.

As we want to return a string, and the results returned by getYear() and getMonth() are integer or number values, we use the Javascript toString() method to convert these numbers to strings. Finally, we need to concatenate the converted strings and the separating character to get the final result. This Expression, although doing many different things such as retrieving portions of a date value, adding offsets, converting numbers to strings, and then concatenating strings together, returns only a single result.

So, let's get our hands on this. In the following example, we are going to add a line number to the order detail table.

  1. Open the Customer Orders report. How to create this report can be read from the Styles, Themes, and Templates chapter, which is available at free download on Packt site.
  2. Insert a column to the left of the OrderNumber column.
    Expressions
  3. Insert a Data element in the cell for the details band:
    Expressions
  4. In the Expression field, enter the following expression:
    row.__rownum + 1
    
    Expressions
  5. Click OK to preview the report.
Expressions

There is now a column with the current line number for the report. While not exactly the most attractive addition, the expression works. We have seen expressions such as these throughout the book. In the Customer Orders report, the Report Title is pulled from the rprmReportTitle parameter using the params["rprmReportTitle"] expression. We use the same row number expression in the highlight example in the Customer Orders report as well.

As we saw with the Bar Chart example, expressions are simply JavaScript. Expressions also have access to JavaScript methods, operators, and objects. In the line number example we just saw, we used the addition operator, and in the Bar Chart example, we used the toString(), getMonth(), and getYear() methods. All assignment, math, comparison, and logical operators available in JavaScript are included. Let's look at the following example that shows the JavaScript string methods being used to return the length of a string:

  1. Create a new report called String Length.rptDesign.
  2. Drop a Data element anywhere on the Report Design.
  3. Use the following Expression.
    "This is a test".length
    
  4. Click OK and preview the report.

A simple report with the number 14 should be displayed. The above expression can also be replaced with the following expression.

var testString = "This is a test";
testString.length;

In the example, we are breaking from the one line expression and using multiple lines and a variable. The key is that the last line is returning a single value. All sorts of computations can take place, as long a single value is returned. Now consider we change the expression to the following:

var testString = "This is a test";
var splitString = testString.split(" ");
splitString[3].toUpperCase();

The report will now return only the word TEST. Again, while multiple things are being done in this expression such as the assignment of the string, splitting the string, and converting the element 3 to upper case, only a single result is returned.

At the end of Chapter 6, Report Parameters, we went through an exercise where we set a default parameter, using expressions in BIRT's new expression-based default parameter value mechanism, to set a range of current date minus 20 days as a start date and current date as the end date. This exercise is an example of using more complex expressions.

//bring in the java.util package into the scripting scope
importPackage( java.util );
//create a new GregorianCalendar object
var cal = new java.util.GregorianCalendar();
//set the date to now
cal.setTime(new Date());
//substract 20 days
cal.add(Calendar.DAY_OF_MONTH, -20);
//return the start date
cal.getTime();

In this example, we utilized some Java objects that were in the execution environment's classpath. Any Java object in a classpath can be utilized in BIRT expressions, as long as the importPackage method is called or it is referenced with the Packages.path syntax. For example, we used importPackage( java.util ) to import the java.util package. But we could have omitted that line and just as easily declared the GregorianCalendar as:

var cal = new java.util.GregorianCalendar();

The reason for this is that as the Eclipse environment already has the java.util package loaded, so there is no need to explicitly load it with the importPackage call. The key to complex expressions are that a single value gets returned, as in the result of the last line of our expression, cal.getTime(), which returned a single Date value.

Event Handling

Now if Expressions are simple lines of code that return a single value, how do Event Handlers fit into the scripting world of BIRT? The answer is simple—they do exactly what they claim they do, handle events. What does that mean exactly?

Well, to understand that we need to look at how BIRT reports are generated. BIRT Reports are generated in phases. As explained in the BIRT Report Object Model Scripting Specification at http://www.eclipse.org/birt/phoenix/ref/ROM_Scripting_SPEC.pdf, there are five phases in the report generation lifecycle.

  • Startup
  • Data Transformation
  • Factory
  • Presentation
  • Shutdown
Event Handling

In each one of these phases, particular events are triggered that allow the report developer to override the default behavior of the report generation. This allows us to do all sorts of advanced things with BIRT such as custom filtering of data, dynamically adding or removing BIRT objects to the report, or even building an entire BIRT report through script.

Let's simplify things just slightly. If we look at the dataset element, we can see five different events are associated with it:

  • beforeOpen: Executed before opening the dataset for processing
  • beforeClose: Executed before closing the dataset
  • onFetch: Executed each time a row is retrieved from a data set
  • afterOpen: After the dataset is opened
  • afterClose: After the dataset is closed

Consider we have two things we need to do with the dataset. First, we need to initialize a counter variable to 0 for the number of rows that are going to be processed. Next, we need to add to the counter each time a row is retrieved. So, these things are important. The order in which these events take place is beforeOpen, afterOpen, onFetch, beforeClose, and afterClose. Therefore, in either beforeOpen or afterOpen, we need to set our variable to 0. Then, in the onFetch event, we need to add 1 to our counter. Let's look at an example of how to do that:

  1. Open Customer Orders.rptDesign.
  2. First, we need to define the global variable that we will use for our count. In the report designer, open the Script tab.
    Event Handling
  3. Under the Outline tab, select the root element, Customer Orders.rptDesign.
    Event Handling
  4. In the Event drop down, select initialize. Be aware that initialize can be called twice in certain circumstances. If we are doing heavy processing, we can also consider using the beforeFactory method. But for our example, we are using initialize.
  5. Use the following code to define our global variable:
    var globalCount;
    
    • As we are creating globalCount in the initialize method, it will be global to the entire report.
  6. Select the getCustomerOrders dataset from the Outline tab.
    Event Handling
  7. From the event drop down, select beforeOpen.
    Event Handling
  8. Use the following code to initialize the globalCount variable:
    globalCount = 0;
    
    
    Event Handling
  9. From the event drop down, select onFetch.
    Event Handling
  10. Use the following code to increment the counter:
    globalCount++;
    
    Event Handling
  11. Open the report designer's Layout tab.
  12. Drag a Data element to the bottom of the report design. Use the following expression:
    globalCount
    
    Event Handling
  13. Run the report.
Event Handling

Understanding the order of execution is important. The report's initialize method will get executed before any of the other methods and declare globalCount. Next, the dataset gets generated in the data transform phase calling BeforeOpen and initializing globalCount to 0. During each row request, the globalCount gets incremented by one. And finally, the value of globalCount gets inserted into the data element we dropped into the report design in the factory phase.

Contexts

From any of the report generation phases, we have access to various contexts. For the most part, each of the five phases has its own context. In addition, there are also a few extra contexts—one for the data row and the other for displaying an element.

In BIRT, contexts are used to access objects within the scripting environment. At a high level, one has access to the Report context. The Report Context is a mapping in the BIRT scripting environment to the Java object IReportContext, inside the org.eclipse.birt.report.engine.api.script package. This object allows script developers access to the design object, report parameters, and various other aspects of the report. Many of these objects are shorthanded by other, easier-to-use references such as using the params array to access report parameters. Let's take a look at how to access the report context.

  1. Create a new report called stringLength.rptDesign.
  2. Create a new report parameter called accessMe as a string and assign it the default value of Test Parameter.
    Contexts
  3. Drag a data component over to the report designer, and for the expression, put in valueFromScript.
    Contexts
  4. In the report designer, open the Script tab.
  5. Open the Outline view.
  6. Under the Outline tab, select the root element StringLength.rptDesign.
    Contexts
  7. Open the Palette view.
  8. When the Script tab is open, the Palette view changes to allow quick access to different objects and methods in the same way the expression editor does.
    Contexts
  9. In the Script editor, under the initialize event, put in the following code:
    valueFromScript = params["accessMe"];
    
  10. Take a moment to play around with the Palette. If we double- click on the reportContext under the Context folder, it will automatically put that into the script editor. Also, when we type, we may notice a drop-down box that allows to see all of the objects and properties associated with the reportContext object.
  11. Run the report to see it work.
    Contexts
  12. After running the report, modify the initialize method as follows:
    valueFromScript = reportContext.getParameterValue("accessMe");
    
  13. Run the report.

In the last modification, we didn't use a local context. Instead, we used the global reportContext to access the reports parameters and used the report context's getParameterValue()method to retrieve the value entered in accessMe.

Adding elements to report

Let's now look at using the reportContext to add a new element to the report. In order to do this, we need to change only the initialize method from the last example with the following code:

valueFromScript = reportContext.getParameterValue("accessMe") + " modified";
//import the needed packages from the Java API
importPackage(Packages.org.eclipse.birt.report.model.api);
importPackage(Packages.org.eclipse.birt.report.model.api.elements);
//using the report context, get the design handle
design = reportContext.getReportRunnable().designHandle;
//get the element factory to create the new label
elementFactory = design.getElementFactory();
dataElement = elementFactory.newLabel("testElement");
//set the text from the valueFromScript variable and add
//to the report designs body
dataElement.setText(valueFromScript);
design.getDesignHandle().getBody().add(dataElement);

So, let's take a look at this example. The valueFromScript variable is assigned from the report parameter, retrieved from the reportContext, similar to the previous example. The next two lines are not really necessary, but illustrate that we will be using objects retrieved from the BIRT API packages. Any time we reference Java objects in code, we should use the importPackage method to reference the package containing those objects. For example, if we were to use the java.util.GregorianCalendar object, we should use importPackage(Packages.java.util).

The next step is to retrieve the Design Handle from the report context. First, we use the getReportRunnable, which is a reference to the open report design. Then we use the designHandle reference to get the report design handle.

The next two lines retrieve the report element factory, which is a convenient way to create report design elements from the design handle. It then uses that element factory to create a new Label element called testElement. We then set the text of that label to valueFromScript.

The last part of the script needs to reference the getBody method in order to get a slot in the report to add a new element to. We then add the new element to the report.

Now, where we add elements is important. Recollect the phases from earlier? Well, one cannot add new elements after the factory phase. This means if we look at the Report Root, we can use the earlier code snippet only in the initialize, beforeRender, and the beforeFactory events. The afterFactory and afterRender events will not work. If we move the code to those elements, then the element will not get added.

If one looks in the beforeFactory event, he/she will notice that the context that points to the IReportDesign object is available, but it is not available in any of the other events. When working with reports through scripting, remember that objects are not always going to be available, depending on what point of the report creation cycle we are at.

Removing elements from a report

The easiest way to drop elements from reports is to find the element using the Design Handle's findElement method. This method will return a reference to a DesignElementHandle, referencing the items name used. Let's say we wanted to change the name of the Data element to dataElementToRemove, with the test string expression used earlier in this chapter. The following code will search the report design, find the matching element, and remove it from the report. It is run from the report's initialize method.

reportContext.getReportRunnable().designHandle.getDesignHandle().findElement("dataElementToRemove").drop();

Adding sorting conditions

In addition to adding report elements, it is also possible to add conditions such as Highlights, Maps, and Sorting conditions. The following example will show how to dynamically add sorting conditions to a report, based on the value of a report parameter checkbox:

  1. Create a new report in a BIRT reporting project. Call this new report as customerPaymentDynamicSort.rptdesign.
  2. Bring in the dsClassicCars data source from the library.
  3. Create a new dataset using the following query:
    select
    *
    from
    CUSTOMERS,
    PAYMENTS
    where
    CUSTOMERS.CUSTOMERNUMBER = PAYMENTS.CUSTOMERNUMBER
    and customers.customernumber = ?
    
  4. Name the parameter for the dataset as dsprmCustomerID.
  5. Under the dialog window for the dataset parameter, create and link to a report parameter called rptprmCustomerID. Set it as a textbox entry.
    Adding sorting conditions
  6. Drag-and-drop the newly created dataset over to the report design pane. Delete all columns except for the following:
    • Customer Number
    • Customer Name
    • Payment Date
    • Amount
    Adding sorting conditions
  7. Create a new report parameter called rptprmSortOrder. Set Hidden option checked.
    Adding sorting conditions
  8. Add the following script to the OnPrepare event of the table. There are two versions of this script.

    One for BIRT 2.3 and above:

    importPackage(Packages.org.eclipse.birt.report.model.api.simpleapi);
    if ( params["rptprmSortOrder"].value != null )
    if ( params["rptprmSortOrder"].value.length > 0 )
    {
    var sortCondition = SimpleElementFactory.getInstance().createSortCondition();
    switch (params["rptprmSortOrder"].value)
    {
    case "date" :
    sortCondition.setKey("row["PAYMENTDATE"]");
    break;
    case "price" :
    sortCondition.setKey("row["AMOUNT"]");
    break;
    }
    sortCondition.setDirection("asc");
    this.addSortCondition(sortCondition);
    }
    

    The other for BIRT versions prior to 2.3:

    //We only want to add this into our code when the value is not null for the
    //parameter sort
    if ( params["rptprmSortOrder"].value != null )
    {
    //Bring in the BIRT Report Model API and for CONSTANTS
    importPackage( Packages.org.eclipse.birt.report.engine.api.script.element );
    //Create a dynamic sort condition
    var sortCondition = StructureScriptAPIFactory.createSortCondition();
    //Based on the value of the sort parameter, set the appropriate key value for sorting
    switch (params["rptprmSortOrder"].value)
    {
    //Remember that for the key, we need to use the fully qualified row and field name as a string, not as a value
    case "date" :
    sortCondition.setKey("row["PAYMENTDATE"]");
    break;
    case "price" :
    sortCondition.setKey("row["AMOUNT"]");
    break;
    }
    //set condition to ascending order
    sortCondition.setDirection("asc");
    //Add to the table
    this.addSortCondition(sortCondition);
    }
    
    • There are two versions because the scripting packages changed in BIRT 2.3. I am keeping the previous version here as a reference because there are software packages that embedded the older versions of BIRT.
  9. In the header row, we need to create hyperlinks that will call this report and pass in parameters to tell which column to sort by. So, save the report as it is, otherwise the parameters will not show up in the drill down dialog. Select the PaymentDate column header.
    Adding sorting conditions
  10. In the Property Editor window, from the left-hand side menu, select Hyperlink.
    Adding sorting conditions
  11. Click the Edit button.
  12. Select hyperlink type as Drill-through.
  13. Link to the customerPayment.rptdesign file.
  14. Select the rptprmCustomerID field and set the value to params["rptprmCustomerID"].
  15. Select the rptprmSortOrder parameter from the Report Parameters list and set the value to "date" with the quotation marks.
  16. Set Show target report in to the Same frame option to open report in the same window.
    Adding sorting conditions
  17. Do the same thing for the Amount column, except set the value of rptprmSortorder to "price".

With this done, we can now reorder the report in view time when the user clicks on the date or the amount columns. With a little more logic developed in, we can have the report do both ascending and descending sorts, and even have it refresh the report without having to refresh the viewing page.

Adding sorting conditions

Affecting behavior of report rendering based on condition through script

Sometimes it is preferable to have a single report behave in a particular way based on some environmental factor. Parameters such as Environment Variables, Output Format, and the time of day may affect the way we want a report rendered. In the following example, we are going to use a real life scenario that I encountered when a client wanted a single report to not enable hyperlinks when they rendered to PDF.

  1. Create a new report called PDFhyperlink.rptDesign.
  2. Insert a label component and set the label to My Link.
    Affecting behavior of report rendering based on condition through script
  3. In the Property Editor, set the Hyperlink to http://www.eclipse.org/.
    Affecting behavior of report rendering based on condition through script
  4. With the label component selected, open the Script Editor.
  5. Set the event to onRender.
  6. Use the following code for the onRender script:
    if (reportContext.getOutputFormat().equalsIgnoreCase("PDF"))
    {
    this.setAction(null);
    }
    
    Affecting behavior of report rendering based on condition through script
  7. Save the report.

If we go to the Run menu, and choose View as HTML, we will see that the report has a hyperlink which, when clicked, opens the BIRT homepage. If we view as a PDF, that hyperlink will not exist and it will just be a label.

In the example that we just saw, we are affecting only the onRender event for output format. This is affecting report generation at the presentation phase. The example uses the report context to determine the output format. If the output format is nothing, the above script will take the labels action property, which is of type org.eclipse.birt.report.engine.api.iaction, and set it to nothing or null. This will essentially cancel any actions for this component when the output format is equal to PDF.

Scripted data source

One of the other things we can do with BIRT scripting is create a data source. In the following example, we will create a simple report that will return the numbers 1 through 10 using a scripted data source:

  1. Create a new report called countOneToTen.rptDesign.
  2. Right-click on the Data Sources section under the Data Explorer and choose Scripted Data Source as the type. This type of data source will use pure Java or JavaScript to generate data for a BIRT report.
  3. Create a new dataset called dsCount.
  4. A dialog will pop up for the columns to return. Add one column called cnt of the type Integer.
    Scripted data source
  5. The script editor will open once we click Finish. In the Open event, add the following code:
    reportContext.setGlobalVariable("currentCount", 0);
    
  6. In the fetch method, use the following code:
    var currentCount = reportContext.getGlobalVariable("currentCount");
    if (currentCount < 10)
    {
    currentCount++;
    row["cnt"] = currentCount;
    reportContext.setGlobalVariable("currentCount", currentCount);
    return true;
    }
    return false;
    
  7. In the report designer, drag the dsCount data source to the Report Designer.
  8. Run the report.
Scripted data source

In the example that we just saw, we did a few things in a different but interesting way. First, we created the Scripted Data source. The way this works is that the fetch method needs to return true when data is returned and false when data is not returned. To get data into the returned row into the cnt column, we then use the reportContexts global variable to keep track of the running count.

Using Java objects as Event Handlers

The last thing we are going to look at in this chapter is using Java objects as Event Handlers instead of JavaScript in the script editor. In order to implement Java-based Event handlers, the designer needs to extend the appropriate Event Handler object. For example, if we are going to implement the last example as a Java object, we would need to extend the org.eclipse.birt.report.engine.api.script.eventadapter.ScriptedDataSetEventAdapter class.

Therefore, to do this, let's create a separate Java project in Eclipse. Let's create a class like the following:

package com.birtbook.eventHandler;
Java objectsJava objectsusing, as event handlersimport org.eclipse.birt.report.engine.api.script.IUpdatableDataSetRow;
import org.eclipse.birt.report.engine.api.script.ScriptException;
import org.eclipse.birt.report.engine.api.script.eventadapter.ScriptedDataSetEventAdapter;
import org.eclipse.birt.report.engine.api.script.instance.IDataSetInstance;
public class ScriptedDataSetHandler extends ScriptedDataSetEventAdapter {
private int currentCount;
@Override
public boolean fetch(IDataSetInstance dataSet, IUpdatableDataSetRow row) {
//increment the counter
currentCount++;
if (currentCount < 11)
{
//set the rows value
try {
row.setColumnValue("cnt", currentCount);
} catch (ScriptException e) {
e.printStackTrace();
}
return true;
}
return false;
}
@Override
public void open(IDataSetInstance dataSet) {
super.open(dataSet);
//initialize the count
currentCount = 0;
}
}

Now, we need to restart Eclipse in order for it to recognize the class in our report design. Then, once we restart Eclipse, we are able to use our class and debug in Eclipse when we run the report. Next, we go into the report design, select my dsCount as the data source, clear all of the script out of the events. Then, under the Event Handler tab in the Property Editor, we click on the Browse… button and select my class. When we run the report, it will use our new class as the event handler.

The benefit to this approach is that it is much easier to debug during development. Plus, we have full access to the Eclipse IDE, code completion, and a much cleaner IDE to develop event handlers with. The drawback is that we would need to deploy the classes with the reports and make sure they are visible in the classpath for our runtime environment.

Using Java objects as Event Handlers

How Chart Event Handling differs from other report items

While Event Handling can be a tricky concept in BIRT, there is something that complicates things even further—charts. Charts use a completely different Event handling mechanism as they are extended item types in BIRT. What this means is that charts are not intrinsic BIRT components. When BIRT executes a Chart, there is a marker saying "Hey, I'm an external component, I need to use the Chart Engine API to execute and Render". Because Charts do not get created until Render time, charts have only an onRender event, with several subevents that internal to the Chart Engine API. This is a little confusing at first. So, let's go through an exercise to illustrate what this means. The following will use BIRT's Chart Event Handler to override the action for a Pie Slice. Pie slices will jump to a corresponding table bookmark for all values except the number 5.

  1. Create a new report called ChartEventOverride.rptDesign.
  2. Add in a Scripted Data Source.
  3. Add a scripted dataset. Add in two columns—one called column of type String, and the other called value of type Integer.
  4. In the open event for the dataset, use the following code:
    count = 0;
    column = 0;
    
  5. For the fetch method, use the following code:
    if (count < 500)
    {
    row["column"] = column;
    row["value"] = count;
    count++;
    if (column > 5)
    {
    column = 0;
    }
    else
    {
    column++;
    }
    return true;
    }
    return false;
    
    
  6. Insert the dataset as a table into the report.
    How Chart Event Handling differs from other report items
  7. Insert a row in to the header above the two column labels and merge the cells.
    How Chart Event Handling differs from other report items
  8. Insert a chart into the new header with the merged cells.
    How Chart Event Handling differs from other report items
  9. Set the type to Pie chart and the output format to PNG.
  10. Select Inherit Data from Container and set this option to Inherit Columns only.
  11. For Category Definition, drag over the id field. For the value series, drag over the count field.
    How Chart Event Handling differs from other report items
  12. Click on the Grouping button next to the Category Series. Set Grouping to Enabled and set the Aggregate Expression to Sum.
    How Chart Event Handling differs from other report items
  13. Select the Format Chart tab. Under the tree view, select Series | Value Series. Click on the Interactivity button.
    How Chart Event Handling differs from other report items
  14. For Event, choose mouse click.
  15. For Action, choose Invoke Script.
  16. Click the Add button.
  17. Put in anything as the name and click on Edit base URL.
  18. For Hyperlink, choose Internal Bookmark. From the drop-down list, select row["column"].
  19. Save the report.
  20. Preview it.

    So right now, we have a basic interactive chart. When we click on any of the pie slices, it executes the hyperlink and jumps to the correct section. Let's now override that behavior through the script:

  21. Select the Chart. Open the Script editor.
  22. Use the following script for the onRender event:
    var oldHandler;
    /**
    * Called before drawing each datapoint graphical representation or marker.
    *
    * @param dph
    * DataPointHints
    * @param fill
    * Fill
    * @param icsc
    * IChartScriptContext
    */
    function beforeDrawDataPoint( dph, fill, icsc )
    {
    //get the value of our currently rendering series
    //dph is of type DataPointHandler in the Chart
    //Engine API
    value = dph.getBaseValue();
    //we need to use reflection to get to the data point implementation
    //normally this isn't necessary, but these values are
    //not exposed. This is complex stuff you don't need
    //to know under normal circumstances. But we need to
    //get access to our current data point so we can
    //override its action behavior
    dphClass = dph.getClass();
    dataPointInstance = dphClass.getDeclaredField("dp");
    dataPointInstance.setAccessible(true);
    implementation = dataPointInstance.get(dph);
    pieInstance = implementation.eContainer();
    //make sure the object we are working with is a pie series impl object
    if (pieInstance.getClass().getName().contains("PieSeriesImpl"))
    {
    //ok, if our value for the current category equals X, then we want to do our action
    if ( value.equals("5"))
    {
    //save the old action. if we dont, any further actions that dont meet the criteria won't
    //execute. then clear the action so it wont execute
    oldHandler = pieInstance.getTriggers().get(0);
    pieInstance.getTriggers().clear();
    }
    else
    {
    //If we cleared the triggers, we need to re-initialize the action
    if (pieInstance.getTriggers().size() < 1)
    {
    if (oldHandler != null)
    {
    pieInstance.getTriggers().add(oldHandler);
    }
    }
    }
    }
    }
    
  23. Save the report and run it. When we run the report, we will notice that the slice for category 5 will no longer jump to the table section as it had its action cleared in the event handler.
How Chart Event Handling differs from other report items

As we can see, chart event handling is much more complex than regular event handling. In the example that we just saw, we had to use Java's reflection mechanism to get access to some properties that are not normally exposed to the end developer in order to change the action of an individual slice. We can also note that there is a submethod called beforeDrawDataPoint that we are implementing. This is an internal Chart Engine API event.

Chart events and the bookmark property for interactivity

In the following exercise, we are going to look at building a very complex chart interactivity example using a combination of chart interactivities, which get rendered as client side JavaScript, text elements containing client side JavaScript, and BIRT Event Handlers to replicate a chart several times for an old fashioned image swap. It will be important that we use either PNG or JPEG charts as SVG already has its own mechanisms for doing this.

  1. Create a report called interactiveChartExample.rptDesign.
  2. Create a new Scripted Data Source.
  3. Add a new Scripted Data Set, with two columns—one for ID and an other called COUNT.
  4. For the open event on the dataset, use the following script:
    count = 0;
    id = 0;
    
  5. For the fetch event, use the following code:
    if (id < 20)
    {
    id++;
    count++;
    row["id"] = id;
    row["count"] = count;
    return true;
    }
    return false;
    
  6. Insert the dataset into the report.
    Chart events and the bookmark property for interactivity
  7. Insert a row above the header row and merge the two cells. This cell will contain our chart.
    Chart events and the bookmark property for interactivity
  8. Select the detail row. In the Property Editor, select the Bookmark tab under Properties. Use the following bookmark: row["id"].
    Chart events and the bookmark property for interactivity
  9. In the footer of the table, under the ID cell, insert a text element. Set the text element's type to HTML. It is important to note that we will be putting in some client side JavaScript code, which will be used in cooperation with a Chart Interactivity. These scripts are not the same as BIRT events, even though both are written in JavaScript.
    <script type="text/javascript">
    /**
    Javascript for setting up the image swap from all the chart instances
    **/
    //Define a new array, which will contain all references to chart images
    //with exploded slices
    imageArray = new Array();
    //the mainImageSrc will be a reference to a chart with no slices exploded, and
    //will be the default image used when no mouse over exists
    mainImageSrc = document.getElementById("Chart-Main").src;
    //we are expecting 20 chart instances, so iterate through all
    for (x = 0; x < 20; x++)
    {
    //current image will be prefixed by "Chart-", plus the number
    currentImage = document.getElementById("Chart-" + (x + 1));
    //get the current image, and hide it using standard HTML style properties
    imageArray[x] = currentImage;
    currentImage.style.display = "none";
    }
    //This function will swap an image, and gets called from a chart Interactivity
    function swapImage(num)
    {
    //get the main image reference. We will be swapping it out. don't worry,
    //the actual image is stored in mainImageSrc
    mainImage = document.getElementById("Chart-Main");
    //swap images with the array
    mainImage.src = imageArray[num - 1].src;
    }
    //set back the main, unexploded pie image
    function showMain()
    {
    document.getElementById("Image-Main").src = mainImageSrc;
    }
    </script>
    
  10. In the footer cell below COUNT, insert another text element, set the type to HTML, and use the following JavaScript. Again, this is client side JavaScript. It creates an HTML tag called tableAnchor. The code will find this div tag and will go up the parent tree to find the TBODY tag, which is inserted when a BIRT table is rendered. This script will highlight a table row when a user hover over an area of the chart.
    <!-- Dummy tag to get parent nodes -->
    <div id="tableAnchor"></div>
    <script>
    // Get the table object itself
    var o = document.getElementById("tableAnchor");
    while(o != null){
    if (o.tagName == "TBODY")
    break;
    o = o.parentNode;
    }
    // Add the mouseover event to each of the table rows
    for (var i = 1; i < o.children.length; i++) {
    var ro = o.children[i];
    ro.onmouseover = function(){highlight(this.id);swapImage(this.id);};
    ro.onmouseout = function(){unhighlight(this.id);};
    }
    // Highlight function is called from mouseove events (see above)
    // and also from chart mouseover events.
    var g_previousHighlight = "";
    function highlight(category) {
    // Remove previous highlight (if any)
    if (g_previousHighlight > "") {
    var o = document.getElementById(g_previousHighlight);
    o.style.backgroundColor="";
    }
    // Apply new highlight
    var o = document.getElementById(category);
    o.style.backgroundColor="#8080ff";
    g_previousHighlight = category;
    }
    function unhighlight(category) {
    // Remove previous highlight (if any)
    if (g_previousHighlight > "") {
    var o = document.getElementById(g_previousHighlight);
    o.style.backgroundColor="";
    }
    // Apply new highlight
    var o = document.getElementById(category);
    o.style.backgroundColor="#FFFFFF";
    g_previousHighlight = category;
    }
    </script>
    
  11. The report so far should look like the following:
    Chart events and the bookmark property for interactivity
  12. Insert a new chart into the top header cell that we created and merged.
    Chart events and the bookmark property for interactivity
  13. Set the type to Pie Chart, with an output format of PNG.
    Chart events and the bookmark property for interactivity
  14. In the Select Data tab, use id as the category definition and count as the slice.
    Chart events and the bookmark property for interactivity
  15. In the Format Chart tab, under Series | Value Series, click the Interactivity button. Change the Event to MouseOver. Set Action to Invoke Script. Use the below script. Again, it is important to note that we are not using BIRT server side scripts here. These are client side, executed in the user's browser at run time. What this does is add in chart interactivity so that when we mouse over a pie chart slice, it will call the swap image and row highlighting code we defined in the two text elements.
    swapImage(categoryData);
    highlight(categoryData);
    
    Chart events and the bookmark property for interactivity
  16. Click Finish.
  17. In the Report Editor, select the Chart instance. Under the Property Editor, choose Bookmarks. Use the following bookmark for the Chart.
    "Chart-Main"
    
  18. Under the General tab in the Property Editor, enter NewChart as the name of the Chart .
  19. Select the report's root element. Open the Script editor, and in the report's initialize event, use the following code. It is important to note that this code executes on server side, before the browser ever sees the final BIRT report. What this code does is find the single chart instance in our report and duplicate it 20 times. For each duplicate, it will explode the pie slice for the matching category name, and name it so that our swap script can find it. Remember, this code executes and is completely unaware of any of the code in either of the two text elements or in the chart interactivity.
    var reportDesignHandle = reportContext.getReportRunnable().getDesignHandle();
    var re = reportDesignHandle.findElement("NewChart");
    var pieChart = re.getReportItem().getProperty("chart.instance");
    for (var x = 1; x <= 20; x++)
    {
    importPackage(Packages.org.eclipse.emf.ecore.util);
    var chartCopy = EcoreUtil.copy(pieChart);
    var outerSeries = chartCopy.getSeriesDefinitions().get(0);
    var innerSeries = outerSeries.getSeriesDefinitions().get(0);
    var pieSeries = innerSeries.getSeries().get(0);
    pieSeries.setExplosionExpression("valueData == " + x);
    pieSeries.setExplosion(5);
    var eih = reportDesignHandle.getElementFactory().newExtendedItem("chart-" + x, "Chart");
    eih.getReportItem().setProperty("chart.instance", chartCopy);
    eih.setBookmark(""Chart-" + x + """);
    re.getContainerSlotHandle().add(eih);
    }
    
  20. Save the report. Preview it. When we mouse over any of the Pie slices, we will see the chart appears to let the slices "jump" out, and the corresponding row will be highlighted in the table. The same thing happens when we mouse over any of the rows.
..................Content has been hidden....................

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