Ajax programming with the Java Scripting API

An excerpt from 'Beginning Java SE 6 Platform: From novice to professional'

1 2 3 4 5 Page 4
Page 4 of 5

Working with XMLHttpRequest

The XMLHttpRequest object is an important part of Ajax, which consists of several technologies that are collectively employed to create interactive Web applications. When a user requests a change in a Web page, this object exchanges a small amount of data with the server (behind the scenes). Because an entire Web page is not reloaded, Web applications appear to be as responsive as desktop applications.

You can use XMLHttpRequest with the Java Scripting API to create interesting Swing applications. For example, these technologies can be used to create Swing mashups (smashups) that combine data from multiple Web services. In this section, I present a Weather application that uses XMLHttpRequest with the Java Scripting API to obtain and display weather data from Yahoo! Weather. Its GUI appears in Figure 1.

The Weather application presents the current weather conditions for a given zip code.
Figure 1. The Weather application presents the current weather conditions for a given zip code

The Weather application's GUI consists of a text field for entering a zip code, and a label for presenting some weather data (including an icon image) associated with that zip code. Type the zip code and press Enter. A few seconds later, you should see the weather data (or an error message if you specify an incorrect zip code).

Listing 6 presents the Weather application's source code.

Listing 6. Weather.java

// Weather.java

import java.awt.*;
import java.awt.event.*;

import java.io.*;

import javax.script.*;

import javax.swing.*;
import javax.swing.border.*;

public class Weather extends JFrame
{
   static ScriptEngine engine;

   public Weather ()
   {
      super ("Weather");
      setDefaultCloseOperation (EXIT_ON_CLOSE);

      // Create a gradient panel in which to present the GUI.

      GPanel pnl = new GPanel ();
      pnl.setLayout (new BorderLayout ());

      // Build input panel for entering a five-digit zip code.

      JPanel pnlInput = new JPanel ();
      Border inner = BorderFactory.createEtchedBorder ();
      Border outer = BorderFactory.createEmptyBorder (10, 10, 10, 10);
      pnlInput.setBorder (BorderFactory.createCompoundBorder (outer, inner));
      pnlInput.setOpaque (false);
      pnlInput.add (new JLabel ("Zip code:"));
      final JTextField txtZipCode = new JTextField (5);
      pnlInput.add (txtZipCode);
      pnl.add (pnlInput, BorderLayout.NORTH);

      // Create and register an action listener with the zip code field. In
      // response to the user pressing Enter, validate this field's content,
      // obtain an input stream to weather.js, make the zip code available to
      // this file's script, and evaluate the script.

      ActionListener al;
      al = new ActionListener ()
           {
               public void actionPerformed (ActionEvent ae)
               {
                  try
                  {
                      String zipcode = txtZipCode.getText ();
                      if (zipcode.length () != 5)
                          throw new NumberFormatException ("five digits " +
                                                           "expected");
                      Integer.parseInt (zipcode);

                      InputStream is;
                      is = Weather.class.getResourceAsStream ("weather.js");
                      if (is == null)
                      {
                          JOptionPane.showMessageDialog (Weather.this,
                                                         "weather.js missing");
                          return;
                      }

                      engine.put ("zipcode", zipcode);

                      InputStreamReader isr = new InputStreamReader (is);
                      engine.eval (isr);
                  }
                  catch (NumberFormatException nfe)
                  {
                      JOptionPane.showMessageDialog (Weather.this,
                                                     "Bad input: " +
                                                     nfe.getMessage ());
                  }
                  catch (ScriptException se)
                  {
                      JOptionPane.showMessageDialog (Weather.this,
                                                     se.getMessage ());
                  }
               }
           };
      txtZipCode.addActionListener (al);

      // Build output label for displaying the weather.

      final JLabel lblWeather = new JLabel ();
      lblWeather.setPreferredSize (new Dimension (300, 200));
      lblWeather.setHorizontalAlignment (JLabel.CENTER);
      inner = BorderFactory.createEtchedBorder ();
      outer = BorderFactory.createEmptyBorder (10, 10, 10, 10);
      lblWeather.setBorder (BorderFactory.createCompoundBorder (outer, inner));
      pnl.add (lblWeather, BorderLayout.CENTER);

      getContentPane ().add (pnl);

      // Make the output label available to the weather.js script.

      engine.put ("lblWeather", lblWeather);

      pack ();
      setResizable (false);
      setLocationRelativeTo (null); // center on the screen
      setVisible (true);
   }

   public static void main (String [] args)
   {
      // Obtain the rhino script engine.

      ScriptEngineManager manager = new ScriptEngineManager ();
      engine = manager.getEngineByName ("rhino");

      // The following if statement is a safety check in the unlikely case
      // that the rhino script engine cannot be found.

      if (engine == null)
      {
          System.err.println ("rhino script engine not available");
          return;
      }

      // Obtain an input stream to ajax.js and evaluate this file's script.

      try
      {
         InputStream is = Weather.class.getResourceAsStream ("ajax.js");
         if (is == null)
         {
             System.err.println ("ajax.js missing");
             return;
         }
         InputStreamReader isr = new InputStreamReader (is);
         engine.eval (isr);
      }
      catch (ScriptException se)
      {
         System.err.println (se);
         return;
      }

      // Create the GUI on the event-dispatching thread.

      Runnable r;
      r = new Runnable ()
          {
              public void run ()
              {
                 new Weather ();
              }
          };
      EventQueue.invokeLater (r);
   }
}

class GPanel extends JPanel
{
   private GradientPaint gp;

   @Override
   public void paintComponent (Graphics g)
   {
      if (gp == null)
          gp = new GradientPaint (0, 0, Color.cyan, 0, getHeight (),
                                  Color.yellow);

      // Paint a nice gradient background with cyan at the top and yellow at
      // the bottom.

      ((Graphics2D) g).setPaint (gp);
      g.fillRect (0, 0, getWidth (), getHeight ());

   }
}

Inside the Weather application

The most interesting parts of Listing 6 involve the Java Scripting API. For starters, the main() method in Listing 6 obtains the Rhino script engine via manager.getEngineByName ("rhino"). It then uses this script engine to load a modified version of the Java Scripting Project's ajax.js script file and evaluate its script, which defines the XMLHttpRequest object.

XMLHttpRequest is accessed by the script stored in weather.js. When the user presses Enter, txtZipCode's action listener receives an action event. After making the entered zip code available to weather.js via the zipcode script variable, the listener loads weather.js and evaluates its script, which accesses zipcode.

Two fixes to the ajax.js script
Although you can obtain ajax.js from the Java Scripting Project, I've included a slightly modified version of this file with the rest of the article's code packet. My modified script does the following:
  • Replaces case 'responseTex' with case 'responseText'. XMLHttpRequest includes a responseText property -- not a property named responseTex.
  • Relocates this._status = this._connection.responseCode; this._statusText = this._connection.responseMessage from the end of function sendRequest() to the start of function readResponse().
The relocation allows weather.js to be evaluated on the event-dispatching thread without slowing down this thread. Without the relocation, the event-dispatching thread is delayed until this._connection.responseCode completes.
1 2 3 4 5 Page 4
Page 4 of 5