Chapter 9. Using JavaScript to Write an Event Handler

BIRT scripting is based on the Mozilla Rhino implementation of JavaScript, also called ECMAScript. Rhino implements ECMAScript version 1.5 as described in the ECMA standard ECMA-262 version 3. The complete specification for Rhino is located at:

http://www.ecma-international.org/publications/standards/
   Ecma-262.htm.

Using BIRT Report Designer to enter a JavaScript event handler

You can use BIRT Report Designer to enter a JavaScript event handler and associate it with a specific event for a specific element.

How to use BIRT Report Designer to enter a JavaScript event handler

  1. In Outline, select the report element, data source, or data set for which you want to write an event handler.

  2. Choose the Script tab.

  3. Choose an event handler from the drop-down list of methods.

  4. Enter the event handler code in the script editor.

Figure 9-1 demonstrates entering a line of code in the onPrepare( ) method of a Table element.

Code entry for the onPrepare() method

Figure 9-1. Code entry for the onPrepare() method

Creating and using a global variable

JavaScript has global variables and local variables. A local variable can only be accessed in the scope of the method in which it is created. You use the var identifier to create a local variable in JavaScript, as shown in the following line of code:

var localCounter = 0;

To create a global variable, you omit the var identifier, as shown in the following line of code:

globalCounter = 0;

When you create a global variable in JavaScript, that variable is visible to all other JavaScript code that executes in the same process. For example, you can use a global variable to count the detail rows in a table by first creating a global variable in the onCreate( ) method of the table, as shown in the following line of code:

rowCount=0;

Since rowCount is global, the onCreate( ) method of the detail row can access and increment it, as shown in the following line of code:

rowCount++;

Understanding execution phases and processes

As explained in the scripting overview chapter, there are three BIRT execution phases: the preparation phase, the generation phase, and the presentation phase. However, there can be either one or two execution processes. When a report is run in the BIRT Report Designer previewer, there is only one execution process. There are two execution processes when the report is run in the interactive viewer or when the report is deployed to an application server. The first process, called the factory process, contains the preparation phase and the generation phase. The second execution process, called the render process, contains only the presentation phase. The render process can occur at a much later time than the factory process and possibly on a different machine.

Because variables are only visible in the process in which they were created, it is important to know which event handlers run in which process. It is also important to be aware that code that works when the report is run in the previewer might not work at run time if there is a render process dependency on a variable created in the factory process.

The event handlers that run in the factory process, in the order executed, include:

  • ReportDesign.initialize( )

  • onPrepare( ) methods for every report item

  • ReportDesign.beforeFactory( )

  • DataSource.beforeOpen( )

  • DataSource.afterOpen( )

  • DataSet.beforeOpen( )

  • DataSet.afterOpen( )

  • DataSet.onFetch( )

  • onCreate( ) methods for every report item

  • DataSet.beforeClose( )

  • DataSet.afterClose( )

  • DataSource.beforeClose( )

  • DataSource.afterClose( )

  • ReportDesign.afterFactory( )

The event handlers that run in the render process, in the order executed, include:

  • ReportDesign.initialize( )

  • ReportDesign.beforeRender( )

  • onRender( ) methods for every report item

  • ReportDesign.beforeFactory( )

  • ReportDesign.afterRender( )

It is worth noting that ReportDesign.initialize( ) runs in both processes.

Using the reportContext object

Almost every event handler has access to an object called the reportContext object. The four exceptions are the open( ) and close( ) event handlers for ScriptedDataSource and ScriptedDataSet. Table 9-1 lists commonly used reportContext object methods.

Table 9-1. reportContext object methods

Method

Task

deleteGlobalVariable( )

Deletes a global variable created using setGlobalVariable( )

deletePersistentGlobalVariable( )

Deletes a persistent global variable created using setPersistentGlobalVariable( )

getAppContext( )

Returns the application context

getConfigVariableValue( )

Returns the value of a config variable

getGlobalVariable( )

Returns a global variable created using setGlobalVariable( )

getHttpServletRequest( )

Returns the HTTP servlet request object

getLocale( )

Returns the current locale

getMessage( )

Returns a localized message from the localization resource file

getOutputFormat( )

Returns the format in which the report is emitted, either html or pdf

getParameterValue( )

Returns a parameter value

getPersistentGlobalVariable( )

Returns a persistent global variable created using setPersistentGlobalVariable( )

setGlobalVariable( )

Creates a global variable that can be accessed with getGlobalVariable( )

setParameterValue( )

Sets the value of a named parameter

setPersistentGlobalVariable( )

Creates a persistent global variable that can be accessed using getPersistentGlobalVariable( )

Passing a variable between processes

Although a global JavaScript variable cannot be passed between processes, there is a way to pass a variable from the factory process to the render process. The setPersistentGlobalVariable( ) method of the report context object creates a variable that can be accessed using the getPersistentGlobalVariable( ) method. The only restriction on the variable is that it must be a serializable Java object.

Getting information from an HTTP request object

The HTTP servlet request object contains various methods to retrieve information about the request to run the report. One useful method of the HTTP request object gets the query string that follows the path in the request URL. The query string contains all the parameters of the request. By parsing the query string, your code can extract the parameters in the request URL to conditionally determine the report output. This feature can be used to pass in a user ID, for example, or to set or override a report parameter.

The following code gets the query string:

importPackage( Packages.javax.servlet.http );
httpServletReq = reportContext.getHttpServletRequest( );
formatStr=httpServletReq.getQueryString( );

Using the this object

Every JavaScript event handler is associated with a particular ROM element, such as a report item, a data source, a data set, or the report itself. Most report elements have properties that an event handler can access and, in some cases, change. Many report elements also have functions that you can call. A JavaScript event handler can access these properties and functions through a special object called the this object.

Using the this object’s methods

The this object represents the element for which the event handler handles events. To use the this object, type the word, this, followed by a period in the script window for the event handler you are writing. At the time you type the period, a window pops up containing a scrollable list of all the properties and functions of the element, as shown in Figure 9-2.

Using the this object to display a list of functions and properties

Figure 9-2. Using the this object to display a list of functions and properties

If the pop-up window disappears at any time, delete the period and re-enter it. Scroll down using the arrow keys or the scroll bar controls and press Enter or double-click when the property or function you want is highlighted.

Using the pop-up window to select a method or property can also be used for other objects. A procedure later in this chapter steps you through setting the background color of a label to yellow.

Using the this object to set the property of a report item

The following procedure assumes that you have a BIRT report that contains a label. The procedure sets the label’s background color to yellow. The general process explained in this procedure is not specific to the label report item, however. You modify all report item event handlers in the same general way.

How to set a property of a report item using JavaScript

  1. Select the label whose color you want to change by navigating the Outline view and selecting the appropriate report item, as shown in Figure 9-3.

    Selecting a report item to modify

    Figure 9-3. Selecting a report item to modify

  2. Select onPrepare from the drop-down list in the Script window, as shown in Figure 9-4.

    Selecting onPrepare( )

    Figure 9-4. Selecting onPrepare( )

  3. Enter the word this, followed by a period in the onPrepare script window, as shown in Figure 9-5.

    Using the this object

    Figure 9-5. Using the this object

  4. Select the getStyle( ) method from the scrollable list of properties and functions.

    The onPrepare script window appears as shown in Figure 9-6.

    The onPrepare script window

    Figure 9-6. The onPrepare script window

  5. Move the cursor to the end of the line in the onPrepare script window and type a period.

    The scrollable list of properties and functions of the Style element appears, as shown in Figure 9-7.

    Properties and functions of the Style element

    Figure 9-7. Properties and functions of the Style element

  6. Select backgroundColor from the list of Style properties and functions.

  7. Complete the line of JavaScript in the onPrepare script window by appending ="yellow", as shown in Figure 9-8.

    Changing the color of an element

    Figure 9-8. Changing the color of an element

  8. Choose the Preview tab to see the effect of the onPrepare event handler script.

    The label appears in the report with a yellow background, as shown in Figure 9-9.

    Preview of the color change

    Figure 9-9. Preview of the color change

Using the row object

The row object provides access to the columns of the current row from within the DataSet.onFetch( ) method. You can retrieve the value of any column, using the column name in a statement similar to the following examples:

col1Value = row["custNum"];
col1Value = row.custNum;

You can only index the column position with the column name if the name is a valid JavaScript name with no spaces or special characters. Alternatively, you can use the column alias if the alias is a valid JavaScript name.

You can also get a column value by numerically indexing the column position, as shown in the following statement:

col1Value = row[1];

When you index the column position numerically, the number inside the brackets is the position of the column, beginning with 1. You can retrieve the row number with row[0].

Although you use array syntax to access the row object in JavaScript, this object is not a JavaScript array. For this reason, you cannot use JavaScript array properties, such as length, with the row object.

Getting column information

The DataSet object has a method called getColumnMetaData( ), which returns an IColumnMetaData object. The IColumnMetaData class has methods that provide information about the columns in a data set, as shown in Table 9-2.

Table 9-2. Methods of the IColumnMetaData class

Method

Returns

getColumnAlias( )

The alias of the specified column.

getColumnCount( )

The number of columns in a row of the result set.

getColumnLabel( )

The column label.

getColumnName( )

The column name at the specified index.

getColumnNativeTypeName( )

One of the following data types:

  • BOOLEAN

  • DATETIME

  • DECIMAL

  • FLOAT

  • INTEGER

  • STRING

The data type is null if the column is a computed field or if the type is not known.

getColumnType( )

The data type of the column at the specified index.

getColumnTypeName()

The data type name of the column at the specified index.

isComputedColumn( )

True or false, depending on whether the column is a computed field or not.

You get the IColumnMetaData object from the dataSet object, as shown in the following statement:

columnMetaData = this.getColumnMetaData( );

You can use the count of columns to iterate through all the columns in the data set, as shown in the following example:

colCount = columnMetaData.getColumnCount( );
for ( i = 0; i < colCount; i++ )
{
   pw.println( "Column val for col position " + i + " = " +
      row[i] );
   pw.println( "Column name for col position " + i + " = " +
      columnDefinitions[i].name );
   }

Getting and altering the query string

You get the text of the query in any DataSet event handler as shown in the following example:

query = this.queryText;

You can modify a query in the DataSet beforeOpen( ) event handler by setting the value of the queryText string. To change the query, set the queryText string to a valid SQL query, as shown in the following example:

queryText = "select * from CLASSICMODELS.CUSTOMERS WHERE
   CLASSICMODELS.CUSTOMERS.CUSTOMERNUMBER BETWEEN 470 AND 490";

One advantage of dynamically altering the query is that you can use business logic to determine the proper query. This approach can be more flexible than using parameters.

Getting a parameter value

A script can get the value of a report parameter by passing the name of the parameter to the getParameterValue( ) method of the reportContext object. The following statement gets the value of the UserID parameter:

userID = reportContext.getParameterValue( "UserID" );

Changing the connection properties of a data source

You can change the run-time connection properties of a data source by accessing the extensionProperties array of the DataSource object. The ODA extension defines the list of connection properties that can be set at run time. Table 9-3 describes the JDBC data source properties that affect the connection at run time.

Table 9-3. JDBC data source run-time connection properties

Property

Description

odaUser

The login user name

odaPassword

The login password

odaURL

The URL that identifies the data source

odaDriverClass

The driver class for accessing the data source

To change these properties, add code similar to the following statements in the DataSource.beforeOpen method:

extensionProperties.odaUser = "JoeUser";
extensionProperties.odaPassword = "openSesame";
extensionProperties.odaURL = "jdbc:my_data_source:xxx";
extensionProperties.odaDriverClass =
   "com.companyb.jdbc.Driver";

Determining method execution sequence

You can determine the sequence of method execution by writing code that generates a file containing a line for every method that you want to track.

To create an output file containing a sequence of method execution, include initialization code in the ReportDesign.initialize method and finalization code in the ReportDesign.afterFactory method. In each method that you want to track, add code to write a line of text to the output file. It is easier to write the code in JavaScript than Java, but it is possible to write analogous code in Java.

The following sections show you how to use JavaScript to determine method execution sequence.

Providing the ReportDesign.initialize code

The following code in the ReportDesign.initialize method creates a file on your hard drive and adds one line to the file.

importPackage( Packages.java.io );
fos = new java.io.FileOutputStream( "c:\logFile.txt" );
printWriter = new java.io.PrintWriter( fos );
printWriter.println( "ReportDesign.initialize" );

The preceding code does the following tasks:

  • Imports the Java package, java.io

  • Creates a file output stream for the file you want to create

  • Creates a PrintWriter object that every method can use to track method execution sequence

How to provide code for the ReportDesign.initialize method

You provide code for the ReportDesign.initialize method by performing the following steps:

  1. Choose the Script tab.

  2. Choose the Outline view.

  3. In Outline, select the top line, as shown in Figure 9-10.

    Selecting the report design

    Figure 9-10. Selecting the report design

  4. In Script, select the Initialize( ) method.

  5. Type the code into the script editor.

    The BIRT report designer appears, as shown in Figure 9-11.

    Providing ReportDesign.initialize code

    Figure 9-11. Providing ReportDesign.initialize code

Providing the code for the methods you want to track

For every method that you want to track, provide a single statement generating a line of output to your log file, as shown in the following statement:

printWriter.println( "Table.onRow" );

To provide code for a report item method you want to track, first select the appropriate object from Outline and select the appropriate method from the method selection list. Then use the same steps for entering code into a method, as described in the preceding section.

To provide code for a data set or the data source method, select the appropriate data source or data set from Data Explorer before selecting the method you want to track.

Providing the ReportDesign.afterFactory code

The following statement in the ReportDesign.afterFactory method closes the file.

printWriter.close( );

Using this method flushes all the buffers and ensures that all method output appears in the file.

To provide the ReportDesign.afterFactory code, select the top line of the outline and select the afterFactory method on the code page.

Tutorial 1: Writing an event handler in JavaScript

This tutorial provides instructions for writing a set of event handlers. The tutorial assumes that you have a basic report design based on the Classic Models, Inc. sample database. The only requirement of the starting report design is that it contains a table of customers with a column for the customer name. In this tutorial you count the customers whose names contain the string “Mini” and display the result in a pop-up window.

In this tutorial, you perform the following tasks:

  • Open the report design.

  • Create and initialize a counter in the Table.onCreate( ) method.

  • Conditionally increment the counter in the Row.onCreate( ) method.

  • Display the result, using the ReportDesign.afterFactory( ) method.

Task 1: Open the report design

Open a report design that uses the Classic Car sample database and displays a table of customer names.

  1. If necessary, open Navigator by choosing Window→Show View→ Navigator.

  2. Double-click the appropriate report design. The file opens in the layout editor, as shown in Figure 9-12.

    Report design in the layout editor

    Figure 9-12. Report design in the layout editor

Task 2: Create and initialize a counter in the Table.onCreate( ) method

In order to count the number of customers whose names contain the string “Mini,” you must first set a persistent global variable to zero. The Table.onCreate( ) method is the most appropriate place to do this task because Table.onCreate( ) executes before any rows are retrieved. You conditionally increment this counter in the Row.onCreate( ) method.

  1. In Layout, select the table by placing the cursor near the bottom-left corner of the table. The table icon appears, as shown in Figure 9-13.

    Table icon in the layout editor

    Figure 9-13. Table icon in the layout editor

  2. Choose the Script tab. The script window appears, as shown in Figure 9-14.

    Script window

    Figure 9-14. Script window

  3. Type the following line of code in the script window for the onCreate( ) method:

    reportContext.setPersistentGlobalVariable("cmKey",
       new java.lang.Integer("0"));
  4. To run the report and verify that the code did not create any errors, choose Preview.

  5. Scroll to the bottom of the report, where JavaScript error messages appear. If there are no errors, the report appears, as shown in Figure 9-15.

    If you see an error message, you may have typed a statement incorrectly. If so, go back to the script window, select the method you just modified, correct the error, and choose Preview again.

    Report preview

    Figure 9-15. Report preview

Task 3: Conditionally increment the counter in the Row.onCreate( ) method

To count the number of customers with the string “Mini” in their names, you must examine each customer’s name and add one to the counter for every occurrence. A logical place to do this task is in the Row.onCreate( ) method, which is executed upon every retrieval of a row of data from the data source.

  1. In Layout, select the row and then choose Script.

  2. Pull down the list of methods at the top of the script window and select onCreate, as shown in Figure 9-16.

    onCreate( ) in the Script window

    Figure 9-16. onCreate( ) in the Script window

  3. Enter the following line of JavaScript code in the script window:

    row_data = this.getRowData();

    Notice that when you enter the period after this, a pop-up appears containing all the available methods and properties, including getRowData. This line of code gets an instance of IRowData. You use the method, getExpressionValue( ), on IRowData to get the contents of a column of the row.

  4. Type the following line of JavaScript below the line you just entered:

    CustName=row_data.getExpressionValue( "row[CUSTOMERNAME]" );

    This line of code returns the contents of the table column that comes from the CUSTOMERNAME column in the data set.

  5. Type the following lines of code to conditionally increment the persistent global variable you created in Task 2: “Create and initialize a counter in the Table.onCreate( ) method.”

    if( CustName.indexOf( "Mini" ) != -1 ){
      cnt = reportContext.getPersistentGlobalVariable("cmKey");
      reportContext.setPersistentGlobalVariable("cmKey",
         new java.lang.Integer(cnt.intValue() + 1 ) );
      }

    You can use the JavaScript palette to insert each of the following elements in the preceding line:

    • indexOf( )

      Select Native ( JavaScript ) Objects→String Functions→indexOf( )

    • != and +=

      Select Operators→Comparison→!= and Operators→Assignment→+=

  6. Choose Preview to run the report again to verify that the code you entered did not create any errors.

Task 4: Display the result, using the ReportDesign.afterFactory( ) method

To display the count of customers with the string “Mini” in their names, you insert code in a method that runs after the processing of all the rows in the table. One logical place for this code is in the ReportDesign.afterFactory( ) method.

  1. In Outline, select the report design, as shown in Figure 9-17.

    Selecting the report design in Outline

    Figure 9-17. Selecting the report design in Outline

  2. Select the afterFactory( ) method from the script window drop-down list.

  3. Type the following code into the afterFactory( ) method:

    importPackage( Packages.javax.swing );
    countOfMinis = reportContext.getPersistentGlobalVariable(
       "cmKey").intValue();
    frame = new JFrame( "Count of Minis = " + countOfMinis );
    frame.setBounds( 310, 220, 300, 20 );
    frame.show( );
  4. Select Preview to see the results. If there were no errors in the code, you see a report similar to the one in Figure 9-18.

    Result of changing the afterFactory( ) method

    Figure 9-18. Result of changing the afterFactory( ) method

    If you do not see the Count of Minis window, look for it behind the Eclipse window. If the Count of Minis window does not appear, the most likely reason is a scripting error caused by an error in one of your code entries. If you suspect that a scripting error has occurred, scroll to the bottom of the report, where all scripting error messages appear. In most situations, there is a brief error message next to a plus sign ( + ). The plus sign indicates that there is a more detailed error message that you can view. To expand the brief error message, choose the plus sign. Scroll down to see the more detailed error message.

Calling Java from JavaScript

Rhino provides excellent integration with Java classes, allowing BIRT scripts to work seamlessly with business logic written in Java. Wrapping Java in JavaScript allows the developer to write powerful scripts quickly by leveraging both internal and external libraries of existing Java code. You can use static methods, non-static methods, and static constants of a Java class.

Understanding the Packages object

The Packages object is the JavaScript gateway to the Java classes. It is a top-level Rhino object that contains properties for every top-level Java package, such as java and com. Packages also contains a property for every package that it finds in its classpath. You use Packages to access a Java class for which Packages has a property by preceding the class name with Packages, as shown in the following statement:

var nc = new Packages.javax.swing.JFrame( "MyFrame" );

You can also use Packages to reference a Java class that is not a part of a package, as shown in the following statement:

var nc = new Packages.NumberConversion( );

For BIRT to find your custom Java class or package, you must place it in the BIRT classpath, as discussed later in this chapter.

Understanding the importPackage method

You can avoid writing a fully qualified reference to a Java class by using the top-level Rhino method importPackage( ). The importPackage( ) method functions like a Java import statement. Use the importPackage( ) method to specify one or more Java packages that contain the Java classes that you need to access, as shown in the following example:

importPackage( Packages.java.io, Packages.javax.swing );

You must prepend Packages to the name of each package. After the first time BIRT executes a method containing the importPackage( ) method, the specified packages are available to all succeeding scripts. For this reason, you should include the importPackage( ) method in the ReportDesign.initialize method, which is always the first method that BIRT executes.

Java imports java.lang.* implicitly. Rhino, on the other hand, does not import java.lang.* implicitly because JavaScript has several top-level objects with the same names as some classes defined in the java.lang package. These classes include Boolean, Math, Number, Object, and String. Importing java.lang causes a name collision with the JavaScript objects of the same name. For this reason, you should avoid using importPackage( ) to import java.lang.

Using a Java class

To use a Java class in a BIRT script, you set a JavaScript object equal to the Java object. You then call the Java class methods on the JavaScript object. The following example creates a Java Swing frame and sets the JavaScript object named frame to the Java JFrame object. Then the code calls the setBounds( ) and show( ) methods directly on the JavaScript object.

importPackage( Packages.javax.swing );
frame = new JFrame( "My Frame" );
frame.setBounds( 300, 300, 300, 20 );
frame.show( );

The effect of this code example is to display a Java window on your desktop containing the title, My Frame.

Placing your Java classes where BIRT can find them

For the BIRT report viewer to find your Java classes, the classes must be in a folder under:

$ECLIPSE_INSTALLplugins org.eclipse.birt.report
   .viewer_*irtWEB-INFclasses

You can put a JAR file and individual classes at this location. If your Java class is a part of a package, you must create a hierarchy of folders under the classes folder that represents the package hierarchy. For example, if your Java class is in the com.acme.businessLogic package, your class must be in:

$ECLIPSE_INSTALLpluginsorg.eclipse.birt.report
   .viewer_*irtWEB-INFclassescomacmeusinessLogic

When you deploy your report to an application server, you must also deploy your Java classes.

Issues with using Java in JavaScript code

There are many nuances of writing Java code, such as how to handle overloaded methods, how to use interfaces, and so forth. Refer to the Rhino page on scripting Java at http://www.mozilla.org/rhino/ScriptingJava.html for more information about these topics.

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

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