Skip to main content

skip to main content

developerWorks  >  Java technology  >

alt.lang.jre: Harnessing Rhino

Learn the fundamentals of JavaScript on the Java platform

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Introductory

Michael Squillace (masquill@us.ibm.com), Software Engineer, IBM

02 Nov 2004

JavaScript is well known as a language for dynamically manipulating and accessing the content of Web pages. With the introduction of Rhino, a 100% pure Java implementation of JavaScript, many developers have discovered that the language is also an excellent tool for quickly building and deploying GUI-based applications. In this fifth article in the alt.lang.jre series, regular developerWorks contributor Michael Squillace introduces you to the fundamentals of Rhino, a prototype-based alternative to the Java language on the Java platform.

Rhino is an open-source implementation of JavaScript written in the Java language. Like many other languages in this series, Rhino is a dynamically typed, object-based scripting language that permits easy access to the vast Java class library. Rhino borrows numerous syntactic aspects from JavaScript to enable you to quickly write powerful programs. Most notably, Rhino omits the statement terminator (;), loosens the rules for declaring variables, and greatly simplifies the syntax for modifying and retrieving object properties (without resorting to calls to accessor methods).

Because it's a Java-based implementation of JavaScript, Rhino should be especially easy for Java developers to pick up. JavaScript's (and thus Rhino's) syntax is quite similar to that of the Java programming language. Both languages employ the same loop and conditional constructs as the Java programming language and follow similar syntactic patterns for expressing these constructs.

While Rhino has much in common with the other alternate languages profiled in this series, it also brings something unique (and, perhaps, somewhat foreign) to your programming efforts on the Java platform. Rhino is a prototype-based, rather than a class-based, language. In Rhino, you build objects, not classes. This lets you avoid the development, deployment, and execution costs of building and manipulating classes in addition to the object instances of those classes. As you'll discover in the course of this article, prototype-based languages such as Rhino can be especially effective when it comes to developing and running GUI-based applications.

Getting started with Rhino

You can download the latest release of the Rhino engine (Rhino-1.5r5 as of this writing) from the Mozilla Web site (see Resources). Simply unzip the archive into a directory of your choosing. A top-level directory named rhino1_5r5 is a part of the archive. The directory contains documentation, examples, the source code, and the js.jar file, which should be included on your classpath.

I'll make extensive use of the interactive shell, which is implemented in the org.mozilla.javascript.tools.shell package. The shell can be invoked to run in either interactive mode (in which case you can enter expressions to be evaluated or blocks of code to be executed) or batch mode. In batch mode, the -e option can be used to run a string containing JavaScript/Rhino source and the -f option can be used to execute a file containing script code. For example, entering the following command will invoke the interpreter in interactive mode:

About this series

While most readers of the alt.lang.jre series are familiar with the Java language and how it runs on a cross-platform virtual machine, fewer may know that the Java Runtime Environment can host languages besides the Java language. This series of articles is a survey of many of the alternate languages for the JRE. Most of the languages explored here are open source and may be used for free, while a few are commercial products that must be purchased. All of the languages introduced in this series are supported by the JRE and are believed by the authors to enhance the dynamic and flexible nature of the Java platform.

javaorg.mozilla.javascript.tools.shell.Main 

You should then see the version number of the interpreter followed by the prompt, js>. To exit the shell select Ctrl+Z (on Windows systems) or Ctrl+D (on Unix systems).



Back to top


A bit of history

Before you jump into learning the fundamentals of Rhino, it may be helpful to know something about the origin and purpose of JavaScript, the language that affords Rhino many of its unique features. The history of JavaScript coincides with the ability of Web browsers to dynamically render and manipulate Web-page content. The first version of JavaScript (originally named LiveScript) was released by Netscape Communications Corp. in 1995, as a part of the Netscape Navigator 2.0 Web browser. JavaScript was meant to provide programmers with an easy and intuitive way to write simple scripts that would perform tasks in the context of a Web page. The following year, Microsoft introduced JScript, its own port of JavaScript for Internet Explorer.

Both versions of JavaScript included an object-based API for accessing and manipulating the content of Web pages called the Document Object Model or DOM. A third implementation of this new scripting language, called EcmaScript, was meant to standardize the language itself and the DOM. Unfortunately, neither Microsoft nor Netscape ever fully implemented the EcmaScript standard, and so compatibility issues continue to be an issue to this day.

With the success of the Java programming language by the late 1990s, Netscape planned a release of Javagator, a 100% pure Java implementation of its Navigator browser. Although Javagator never came to fruition, the Netscape port of JavaScript, called Rhino, has survived the test of time. Rhino is a 100% pure Java implementation of the JavaScript 1.5 scripting language without the DOM API. In fact, Rhino is sometimes still referred to as Netscape's Java-based JavaScript.



Back to top


The "Java" in JavaScript

With the first release of Rhino, Netscape obviously wanted to capitalize on the new success of the Java programming language. The company clearly borrowed some of the basic syntax of its scripting language from the Java language. This makes it especially easy for Java developers to learn and use. For example, consider the similarities between a Java function and the Rhino function in Listing 1, which tests whether or not a given number is prime:


Listing 1. A Rhino function: Is this number prime?
function isPrime (num)
{
    if (num <= 1) {
        print("Please enter a positive integer >= 2.")
        return false
    }
    
    var prime = true
    var sqrRoot = Math.round(Math.sqrt(num))
    
    for (var n = 2; prime & n <= sqrRoot; ++n) {
        prime = (num % n != 0)
    }
    
    return prime
}

With a few exceptions, this program looks remarkably like a Java program:

  • Curly braces are used to delimit blocks of code.

  • The syntax for for and if constructs is the same as in the Java language.

  • Rhino employs the same arithmetic and conditional operators as the Java language (for instance, the assignment to the sqrRoot variable), and even supports a similar means to access other arithmetic functions.

  • Rhino allows the use of pre-defined boolean literals true and false.

While not shown here, note that the syntax of Rhino's while and do...while loop constructs matches that of the Java language.



Back to top


Differences in the details

There are, of course, some noteworthy differences between Rhino and the Java language. First, because Rhino is a dynamically typed language, you won't find types in its declarations of functions or variables. You use the function keyword to denote the start of a function and the var keyword to declare local (as opposed to global) variables, but you do not include the type of the variable being declared. The Rhino runtime will infer the type during execution. Unlike the Java language, Rhino has no statement terminator (the semicolon in the Java language), although it supports the use of statement terminators as an option.

The other major distinction between Rhino and the Java language is that you can run a program like the one shown in Listing 1 from the interpreter. If you assume that the function definition is in a file called isprime.js for example, then you would simply type the following command at the interpreter prompt, where path specifies the absolute path to the directory in which the file is stored:

load("<path>/isprime.js") 

Upon the return of the prompt, you would type the following command and then immediately receive the result, true, from the Rhino interpreter:

 isPrime(37) 

In this way, the Rhino interpreter can function like a "scratch pad" in which to enter simple blocks of Java code for testing and debugging. Because the syntax of the two languages is so similar, this usually involves cutting the relevant code from the Java program, pasting it into a .js file and loading it into the interpreter, and then invoking it from the shell.

Of course, not all of Rhino's distinctions from the Java language are so subtle. In the sections that follow, you'll see some of the most useful ways that Rhino differentiates itself, starting with its support for literal representations of familiar and frequently used data types, including arrays, hashes (or associative arrays), regular expressions, functions, and, as you shall see, objects themselves.



Back to top


Arrays

The array in Rhino can be represented as a comma-separated list of values between square brackets. Thus, the following are arrays in Rhino:

 
numbers = [0, 1, 2, 3, 5, 24] 
names = ["Mike", "Joe", 
  "Betty", "Therese", "Lynn Marie"] 
arrayOfArrays = [1, [2, 3], [3, 4, 5, [6, 7]], 8] 

The third example demonstrates that elements of an array need not be of the same type.

Associative arrays

The associative array is another data type that can be represented as a literal. An associative array, sometimes referred to in other languages as a dictionary or a hash, is a list of key-value pairs, with the key and value being separated by a colon (:). An associative array in Rhino functions much like a java.util.HashMap in the Java language. The following are associative arrays in Rhino:

 
person = 
  {name:"Mike Squillace", age:37, position:"software engineer"} 
link = {text:"IBM Home", url:"http://www.ibm.com"} 

You can refer to elements of these lists in one of two ways. To set the age property of the just-defined person hash, you could write person["age"] = 39 or person.age = 39. To retrieve the value and store it in a variable named myAge, you could write either myAge = person["age"] or myAge = person.age.

Rhino treats typical indexed arrays as special kinds of associative arrays. They just happen to be associative arrays with positive integers as keys. Thus, the following lines define precisely the same array:

a1 = ["fee", "fi", "fo"]
a2 = {0:"fee", 1:"fi", 2:"fo"}

Iterating through properties

Rhino provides a special loop construct, the for...in construct, for iterating through the properties in an associative array. The following code prints the properties of the person associative array I've just defined, along with the values for these properties:

for (prop in person) {
    print("person[" + prop + "] = " + person[prop])
}

Of course, I'm iterating over a hash-like structure, so there is no guarantee of the order in which the properties and their values will be printed.



Back to top


Regular expressions

Like arrays, regular expressions can be represented literally in Rhino, where their syntax should be familiar to users of Perl and other scripting languages. A regular expression in Rhino is simply delimited by a forward-slash (/) when represented as a literal.

In Rhino, regular expressions are passed to methods of string objects to make text-processing tasks more straightforward. For example, the following code first defines a regular expression that matches either positive integers or the standard arithmetic operations in an arithmetic expression. The second line then tokenizes the given expression by invoking the match function, as shown below:

tokenExpr = /\d+|[\+\-\*\/]/g
"38-4+98/5".match(tokenExpr)

The result is a Rhino array of string elements containing the following:

"38", "-", "4", "+","98",  "/", and "5"



Back to top


Function literals

Finally, Rhino affords you the function data type. As already noted, Rhino supports functions as first-class data types -- they can be returned from and passed to functions and used in variable declarations. Hence, I could write the following at the interpreter prompt and obtain 9, the expected result to define the square function:

square = function (x) { return x * x }

Following this definition of square, I would type

square(3)

Functions defined in this manner can be used anywhere other literals are used. For example, I could define an associative array of functions like so:

fnList = {
  square:function (x) {return x * x},
  cube:function (x) {return x * x * x},
  sqrt:function (x) {return Math.sqrt(x)}
}

I could then iterate over the functions in the list, printing out the values for each as they were applied to a particular number. For instance, if I wrote the following snippet

for (fnName in fnList) {
    print("The " + fnName + " of " + 3 + 
      " is " + fnList[fnName](3))
}

I would obtain something like this:

The square of 3 is 9
the sqrt of 3 is 1.7320508075688772
The cube of 3 is 27



Back to top


Objects in Rhino

Given the associative array and the ability to represent functions as literals, I am able to represent any object as an associative array in Rhino. Indeed, the literal representation of an object is simply an associative array that may contain functions for some of its values. An example will illustrate just how simple and powerful it is to work with objects in Rhino. To start, consider, once again, the person hash:

person = {name:"Mike Squillace", age:37, position:"software engineer"}

This is a literal representation of an associative array in Rhino but it is, more generally, the literal representation of an object. Such a representation is also called an object initializer in Rhino. The type of the value just defined is confirmed by the interpreter which, after I enter the above definition, responds with the following code:

[object Object]


The above code indicates that the value held by the variable person is of type Object.

Adding methods

Next, I'll show you what happens when I redefine the person object to include a function for retrieving the first name of the object. I do this by writing the following:

person = {
  name:"Mike Squillace",
  age:37,
  position:"software engineer",
  getFirstName:function () {return this.name.split(" ")[0]}
}

The function (or, more precisely, method), getFirstName, uses the this pointer to refer to the current object and invokes the split method on the name property. The split method then returns an array holding the strings that resulted from separating the given string into substrings,as delimited by the space character. I then retrieve the first value of this array and return it.

I invoke the new getFirstName function in a familiar manner:

person.getFirstName()

The parentheses indicate to the Rhino interpreter that I'm invoking a function rather than simply referring to a property of the object. Notice, however, that the function itself is just another property of the person object and that, without the parentheses, it would refer to an undefined value.

Adding more methods

Rhino also allows you to add properties and methods to objects on the fly. For example, if I wanted to add a method to retrieve the last name of the person represented by the person object, I would simply type the following:

person.getLastName = function () {return this.name.split(" ")[1]}

Now, when I enter the method below, I'll get the desired and expected result, in this case "Squillace":

person.getLastName()

(Note that you can use the delete operator to remove any property, as in delete person.getLastName.)



Back to top


Working with prototypes

While the above examples are interesting, you probably don't want to keep using object initializers to define individual persons. Fortunately, Rhino provides another way to create objects, using the constructor function. For example, the following function might serve as a constructor function for an object of type Person:

function Person (name, age, job) {
  this.name = name || "<unknown>"
  this.age = age
  this.job = job || "<unemployed>"
  this.getFirstName = function () {return this.name.split(" ")[0]}
  this.getLastName = function () {return this.name.split(" ")[1]}
}

Once I have a constructor function, I can use the new operator to create objects, as shown below:

mike = new Person("Mike Squillace", 37, "software engineer")

Any function can serve as a constructor function, although you'll typically want to use functions that define properties for an object (using the this pointer to refer to the object being defined) and to assign values (or functions) to these properties.

Coding without classes

As a Java developer, you're likely thinking that your next step will be to define the Person class in Rhino. In fact, Rhino doesn't have a way to define classes -- because it doesn't use them! There are no classes or class instances in Rhino, only particular objects. When invoked with the new operator, the constructor function creates what is known as a prototype for an object; that is, it creates a template from which to build objects of the given kind.

Class- vs. prototype-based languages

Class-based languages like C++ and the Java language use a class definition to embody a set of objects with a particular set of properties, including the data used to represent instances of the class and the type of tasks the instances can perform. Once the class definition is written and compiled, it cannot be changed at runtime and all instances of that class have the data and methods (and only the data and methods) defined for that class. In this paradigm, all classes are instances of a class Class.

Prototype-based languages do not distinguish between classes and instances; they recognize only particular objects that are based on a prototype or template. The prototype specifies the properties with which an object is initially created. The properties for any instance based on that prototype or of the prototype itself can be changed at any time. Thus, there is no need for a "class" object or data type. In a sense it can be said that prototype-based languages are based on the dissimilarity, rather than the similarity, of objects.

In a prototype-based language like Rhino, you can modify the properties of either a particular object or its prototype. For example, if I wanted to add a special property to the just-defined mike object, I would do so as follows:

mike.disability = "blind"

I could also modify the Person prototype by referring to the prototype property of the Person constructor function. If I then wanted to add a birthdate property to all objects formed from this constructor function, I would write:

Person.prototype.birthdate = null

This would be followed by:

mike.birthDate = new Date(66, 10, 3)  
  // months are zero-based: this is 11/3/66

Also note that any new objects created based on the Person prototype would have the birthdate property, making the following code valid:

jami = new Person("Jami Bomer", 25, "unemployed")
jami.birthdate = new Date(79, 5, 28)



Back to top


Building a GUI

I'll close this introduction to Rhino with a working example. Because prototype-based languages are especially useful when it comes to building GUI applications, I'll walk through the steps of building a GUI app in Rhino.

This example is designed to tie together many of the facets and concepts of Rhino discussed so far, and introduce you to a few new ones. Then, I'll review a GUI-based application that is used to enter personnel records for the employees of a large software company. The GUI itself will be the front end of a system that manages employee records for a large corporation. The example will, of course, be far from complete, but it will underscore some of the most important concepts of the Rhino scripting language.

The GUI

The GUI is based on the Java Swing GUI framework and consists of two basic panels. The top panel holds a javax.swing.JTabbedPane containing any number of panels for entering employee data. You're interested in only two of the panels: one that lets you enter general employee information and the other that lets you record each employee's published works, if any. The general information panel is shown in Figure 1:


Figure 1. The general information panel
The general information panel

Figure 2 shows the employee published works panel:


Figure 2. The published works panel
The published works panel

The bottom panel of the main GUI consists of buttons for adding or modifying employee records, for clearing the form fields of a panel, and for exiting the application. The first two of the buttons will behave somewhat differently depending on the panel of information that is currently being edited. For instance, the Clear button should clear all the fields of the panel of the currently active tabbed pane, while the Add button should modify only the data associated with that panel. (The Exit button will always exit the application without saving data.)

To keep things simple, the back-end consists of only two entities. First, I'll construct and modify an associated array whose keys (or indecies) are employee social security numbers. Corresponding to each social security number is the employee data entered on each panel. I'll also use an Employee prototype to hold general information about any employee entered into the first panel of the tabbed pane. All other records will be created and added to the employee instance being edited only as needed.

Building the mainframe

The code for the MainFrame constructor function and for the instantiation and display of the frame is provided in Listing 2:


Listing 2. The constructor function for the Employee GUI mainframe
importPackage(java.awt, java.awt.event)
importPackage(Packages.javax.swing)
importClass(java.lang.System)
:
// the main frame of the GUI:
// consists of a tabbed pane to which other panels for additional employee
// information may be entered
function MainFrame ()
{
    var cp = this.contentPane
    var tabbedPane = new JTabbedPane

    this.gip = new GeneralInfoPanel
    tabbedPane.addTab(this.gip.id, this.gip.infoPanel)
    this.pp = new PublicationsPanel
    tabbedPane.addTab(this.pp.id, this.pp.pubPanel)

    var compMap = {}
    compMap[this.gip.id] = this.gip
    compMap[this.pp.id] = this.pp
    
    var buttonPanel = new JPanel(new GridLayout(1, 3))
    var addButton = new JButton("Add")
    var clearButton = new JButton("Clear")
    var exitButton = new JButton("Exit")

    addButton.setMnemonic(KeyEvent.VK_A)
    addButton.addActionListener(
        new JavaAdapter(
            ActionListener,
            {actionPerformed:function (event) {
                compMap[tabbedPane.selectedComponent.name].doAdd()
            }}
        )
    )
    buttonPanel.add(addButton)
    
    clearButton.setMnemonic(KeyEvent.VK_R)
    clearButton.addActionListener(
        new JavaAdapter(
            ActionListener,
            {actionPerformed:function (event) {
                compMap[tabbedPane.selectedComponent.name].clearFields()
            }}
        )
    )
    buttonPanel.add(clearButton)

    exitButton.setMnemonic(KeyEvent.VK_X)
    exitButton.addActionListener(
        new JavaAdapter(
            ActionListener,
            {actionPerformed:function (event) {
                var confirm = JOptionPane.showConfirmDialog(
                    cp,
                    "Confirm exit",
                    "Confirm Exit Dialog",
                    JOptionPane.YES_NO_OPTION
                )
                if (confirm == JOptionPane.YES_OPTION) {
                    System.exit(0)
                }
            }}
        )
    )
    buttonPanel.add(exitButton)

    cp.add(tabbedPane, BorderLayout.CENTER)
    cp.add(buttonPanel, BorderLayout.SOUTH)
}  // MainFrame
MainFrame.prototype = new JFrame   
:
// display GUI
main = new MainFrame("Employee Record Example")
main.setSize(400, 400)
main.visible = true

Listing 2 begins with three importXXX statements. Two versions of the importPackage statement exist. For importing packages in the java package, I use the importPackage statement with a comma-separated list of the names of the packages I wish to import. For all other packages (such as javax.swing), I must use the Packages object to refer to the package I want to import, as in Packages.javax.swing. Finally, I use the importClass statement to import individual classes.

As I previously mentioned, you can create only individual objects, not classes, in prototype-based languages like Rhino. Here, I'm creating a Rhino MainFrame object that will serve as the mainframe of the GUI. For convenience, I begin by creating two local variables to hold the frame's content pane and the JTabbedPane.

Next, I create the Rhino GeneralInfoPanel object and make it a member of the MainFrame object by assigning it to this.gip. The GeneralInfoPanel will be used for entering general information such as the name, social security number, department, and rank of the employee. Finally, I add the infoPanel (the actual javax.swing.JPanel) as a member of the GeneralInfoPanel object. The infoPanel is added to the tabbed pane with the name given to it by the ID member of the newly instantiated GeneralInfoPanel.

Notice in all of this that the GeneralInfoPanel is not a subclass and does not inherit in anyway from javax.swing.JPanel. Rather, one of its members is a JPanel that I can access via the infoPanel member. (You'll better understand the rationale behind this approach momentarily.)

The component map

The GUI's PublicationsPanel object is processed similarly to the GeneralInfoPanel panel. Once it's completed, I'm ready to create the component map, compMap.

The component map is keyed by the ID of the Rhino Panel objects. Because the buttons of the GUI must perform somewhat differently depending on the panel that is active in the tabbed pane, I want to refer not only to these panels but also to their members. In particular, I want to refer to the methods that perform the functions required when the Add or Clear buttons are pressed. (To put it in terms more familiar to Java developers, you require the Panel objects to implement an interface that includes methods for performing the desired clear and add functions.)

Because the name of the component in the tabbed pane and the key in the component map are both defined by the id attribute of the Panel objects, I can easily access the active panel with the following call:

compMap[tabbedPane.selectedComponent.name].doAdd()

Finally, I create the buttonPanel and the buttons and add everything to the content pane of the JFrame.

A note about event handling

One peculiarity of Rhino is the way it has you attach java.awt.event.ActionListener implementations to each button. For example, consider the ActionListener for the addButton:

addButton.addActionListener(
    new JavaAdapter(
        ActionListener,
        {actionPerformed:function (event) {
            compMap[tabbedPane.selectedComponent.name].doAdd()
        }}
    )
)

The JavaAdapter class of the org.mozilla.javascript package makes it especially easy to add listeners to components in Rhino. A JavaAdapter is created from the interface (or interfaces) to be implemented along with an object that includes a key matching the name of the method(s) to be implemented for the interface(s). The value is the function to be invoked when the event occurs. Here, the function simply passes on the work of the addButton to the doAdd method of the panel that is active in the tabbed pane.

The MainFrame object

Notice, also, that I do not yet have a JFrame for this GUI. In fact, I never really have a JFrame but, rather, a MainFrame Rhino object. The MainFrame object I have just built behaves like a JFrame because of the statement following its constructor function:

MainFrame.prototype = new JFrame

This statement causes the properties and methods of the newly instantiated JFrame object to be shared with your MainFrame object. It is the JFrame object with which you share properties and methods -- I do not inherit or subclass the JFrame class. (Of course, any MainFrame objects I create would also share the properties and methods of the JFrame instance.)

The fact that I am dealing with a Rhino (and not a Java) object means that I could not hope to follow a similar technique when building the Rhino XXXPanel objects. If I were to create these objects and then follow their constructor functions with the statement below, I would not have a JPanel and, hence, could not pass it to the add method of the contentPane object in the MainFrame or to the addTab method of the JTabbedPane:

XXXPanel.prototype = new JPanel

This is another reminder that in Rhino, you work with objects and that the relation between objects is one of sharing properties or methods, not of membership in a hierarchy of classes.

The GeneralInfoPanel and the Employee prototype

The first panel of the tabbed pane in the MainFrame is the GeneralInfoPanel, which contains fields for entering general employee information. For the most part, the code is quite similar to Java code, as demonstrated by the excerpt in Listing 3:


Listing 3. Excerpt of the GeneralInfoPanel constructor function
function GeneralInfoPanel ()
{
    this.infoPanel = new JPanel()
    with (this.infoPanel) {
        setLayout(new BoxLayout(this.infoPanel, BoxLayout.Y_AXIS))
        name = this.id = "general"
        
        var box1 = new Box(BoxLayout.X_AXIS)
        var box2 = new Box(BoxLayout.X_AXIS)
        var box3 = new Box(BoxLayout.X_AXIS)
        
        add(Box.createVerticalGlue())
        box1.add(box1.createHorizontalGlue())
        var nameLabel = new JLabel("Name:")
        nameLabel.horizontalAlignment = JLabel.RIGHT
        box1.add(nameLabel)
        box1.add(box1.createHorizontalStrut(4))
        this.nameField = new JTextField(30)
        nameLabel.labelFor = this.nameField
        box1.add(this.nameField)
        box1.add(box1.createHorizontalStrut(8))
:
}  // GeneralInfoPanel

The chief difference between the above code and Java code is that I have not used inheritance to form the GeneralInfoPanel. Rather, I've made the JPanel an instance member of the object, as should be clear for reasons already discussed.

Adding/modifying employee records

If I were writing Java code, my next step would be to extend JPanel and implement the appropriate listener interfaces all in one class. In Rhino, I mirror this behavior by holding on to the components to be added to the JPanel and assigning functions to data members of this object to handle events. For instance, the addButton of the MainFrame always calls the doAdd method of the panel object. Listing 4 shows the doAdd method for the GeneralInfoPanel:


Listing 4. Code for the doAdd method of the GeneralInfoPanel
  this.doAdd = function () {
    with (this) {
        if (empMap[this.ssnField.getText()] == null) {
            empMap[this.ssnField.getText()] = new Employee(
                nameField.getText(), ssnField.getText(), deptField.getText(),
                cityField.getText(), stateField.selectedItem, rankField.selectedItem,
                payField.getText(), formulaField.getText()
            )
        } else {
            var emp = empMap[this.ssnField.getText()]
            emp.name = nameField.getText()
            emp.ssn = ssnField.getText()
            emp.dept = deptField.getText()
            emp.city = cityField.getText()
            emp.state = stateField.getSelectedItem
            emp.rank = rankField.getSelectedItem
            emp.basePay = parseInt(payField.getText())
            emp.payFunction = new Function (
                "with(this){var pay = basePay; " + 
                  formulaField.getText() + "}; return pay")
        }
    }
}  // doAdd

The next step is to test the results by attempting to retrieve the desired employee. If the record is found, I'll simply modify the record upon an add operation. If the record is not found, I create a new Employee prototype by invoking the Employee constructor function with the appropriate fields from the panel. Here's the Employee constructor function:

// employee prototype
function Employee (name, ssn, dept, city, state, rank, basePay, payFormula)
{
    this.name = name
    this.ssn = ssn
    this.dept = dept
    this.city = city
    this.state = state
    this.rank = rank
    this.basePay = parseInt(basePay)
    this.payFunction = new Function(
        "with(this){var pay = basePay; " + 
          payFormula + "}; return pay")
}  // Employee

The publications record

You have probably noted that the Employee prototype does not include a field for employee publications. While I want to record this data, it's important to account for the fact that not all employees will have published works. If I had written this application in the Java language, the Employee would be required to hold a publications data member upon its compilation; otherwise, no such field could ever be added. In Rhino, however, you can add and modify the Employee prototype as you see fit, as Listing 5 demonstrates:


Listing 5. The doAdd method for the PublicationsPanel
this.doAdd = function () {
    var employee = empMap[main.gip.ssnField.getText()]
    if (employee != null) {
        if (employee.publications == null) {
            employee.publications = []
        }
            
        with (this) {

            employee.publications.push({
                title:titleField.getText(),
                date:new Date(Date.parse(dateField.getText())),
                source:sourceField.getText(),
                coauthor:authField.getText(),
                type:typeField.selectedItem
            })
        }
    }
}  // doAdd

Again, one of the advantages to building objects rather than classes is that you get to avoid the overhead of storing data that isn't there.

The key difference between the PublicationsPanel and the GeneralInfoPanel (besides the obvious differences in required fields) is the doAdd method, which is given in Listing 5. I check first to see if this employee has a publications list. (Rhino always returns the value null as the result of accessing a nonexistent field.) If not, I create an empty list in which to store publication records. If so, I simply push the record onto the existing list. Notice that this field is created only once and only if the Add button is pressed on the PublicationsPanel. I could, of course, add other panels to add or modify additional employee records. In this way, Rhino lets you literally build employee objects step-by-step, adding to and modifying them as needed without worrying about their conformance to a previously defined and static representation.



Back to top


Conclusion

Rhino is a lightweight yet powerful scripting language. Its familiar syntax and extensive use of data types not available in the Java programming language make it an excellent tool for rapid development on the Java platform. As demonstrated in this article, Rhino's use of prototypes, rather than classes, makes it more suited than many scripting languages to developing GUI-intensive applications, especially with regard to performance and style.

Rhino's major weakness is also its strength: Its basis in the Java language makes it both easy to learn and, at times, unduly verbose for a scripting language. Rhino's prototype-based approach is similarly double-edged; the very qualities that make it uniquely suited to some tasks make it seem tedious and cumbersome in other instances. This will be particularly so for the developer who approaches Rhino from a Java-centric perspective.

That said, Rhino is perhaps the most popular of all the scripting languages discussed in this series, and so merits the attention of any curious and well-informed developer. Rhino is also undoubtedly the language of choice when working in the context of a Web browser to dynamically render and manipulate Web content.



Resources

  • Don't miss a single installment in the new alt.lang.jre series! Visit the alt.lang.jre series page for a complete listing of all columns.

  • The IBM Reflexive User Interface Builder (RIB) (developerWorks, August 2004) is an application and toolkit for building and rendering Java GUIs in a variety of scripting languages, including Rhino, Jython, and JRuby. RIB was developed by Michael Squillace and Barry Feigenbaum and is available from IBM alphaWorks.



  • Sing Li explores the potential of EcmaScript and the Free EcmaScript Interpreter in his article on "Quick-and-dirty Java programming" (developerWorks, July 2001).



  • Get a primer on using Standard JavaScript (aka Rhino) for server-side programming, with Chrisopher Vincent's two-part "IBM Lightweight Services, Part 1: Server-side scripting" (developerWorks, November 2002).



  • The Rhino Download page is the site for downloading Rhino. The latest release as of this writing is Rhino 1.5 release 5. Be sure to download the archive that contains the js.jar file; contrary to the instructions on the Web site, most of the other archives contain only the source and the build.xml files.



  • You'll find articles about every aspect of Java programming in the developerWorks Java technology zone.

  • Browse for books on these and other technical topics.


About the author

Author photo

Dr. Michael Squillace is a member of the IBM Worldwide Accessibility Center, where he develops tools for enabling IBM's software products for accessibility. He holds a Ph.D. in philosophy and is currently a student at the University of Texas majoring in computer science. He is a professional pianist. He is also blind. You can contact Dr. Squillace at masquill@us.ibm.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top