Skip to main content

Bringing Swing to the Web

April 20, 2006

{cs.r.title}







There is no doubt that the Java platform has been experiencing something of a revival on the desktop since the release of Java SE 5.0 (Tiger). At no time during Java's history did Swing and the third-party libraries around it generate such an interest and improve at such a rapid pace. You don't need to look far to find evidence of this--there are numerous excellent open source libraries available here on Java.net and on sites such as SourceForge. Some examples of these include the Substance look and feel (shown in Figure 1) used to improve the visual appearance of your application, JGraph (Figure 2) for rendering graph structures, and the excellent set of SwingX components (Figure 3) made available by SwingLabs.

The Substance Look and Feel
Figure 1. The Substance look and feel

A JGraph
Figure 2. A JGraph

One of the latest additions to SwingX - JXGraph
Figure 3. One of the latest additions to SwingX: JXGraph

Unless you are implementing a thick enterprise client such as an applet or a Java Web Start application--options which have their own sets of size, deployment, and compatibility issues--you might think that there is not that much scope for using Swing within web applications, or indeed Java EE. You would, however, be making a mistake; there is no reason why web and Swing development should be mutually exclusive. In this article, we will look at putting one of Swing's most overlooked (and basic) features to work inside a web application.

JComponents as Images

Because of Swing's lightweight approach to component rendering--all visual components are drawn with Java code rather than by the operating system or native code--it is possible to "draw" virtually any component onto an image rather than the screen. This means that any subclass of JComponent, which forms the base of all Swing components, can be rendered to an image.

Accomplishing this is not as difficult as you might think, and requires only a few lines of code. Below is an excerpt of the SwingImageCreator utility class, which we will use to convert Swing components into images.

...
public class SwingImageCreator {

   /**
    * Creates a buffered image of type TYPE_INT_RGB
    * from the supplied component. This method will
    * use the preferred size of the component as the
    * image's size.
    * @param component the component to draw
    * @return an image of the component
    */
   public static BufferedImage createImage(JComponent component){
      return createImage(component, BufferedImage.TYPE_INT_RGB);
   }
  
   /**
    * Creates a buffered image (of the specified type)
    * from the supplied component. This method will use
    * the preferred size of the component as the image's size
    * @param component the component to draw
    * @param imageType the type of buffered image to draw
    *
    * @return an image of the component
    */
   public static BufferedImage createImage(JComponent component,
                                           int imageType){
      Dimension componentSize = component.getPreferredSize();
      component.setSize(componentSize); //Make sure these
                                        //are the same
      BufferedImage img = new BufferedImage(componentSize.width,
                                            componentSize.height,
                                            imageType);
      Graphics2D grap = img.createGraphics();
      grap.fillRect(0,0,img.getWidth(),img.getHeight());
      component.paint(grap);
      return img;
   }
}

The class contains two overloaded methods that accept a JComponent and produce a BufferedImage instance. The system will default to the TYPE_INT_RGB image type if none is specified.

Once you have successfully created a BufferedImage instance, exporting it to a file is a breeze with the ImageIO library. Below are a few examples that demonstrate how to export the image in PNG format to either a file or an OutputStream:

//Rendering a Buffered Image to a File  
ImageIO.write(bufferedImage,"png",new File("my-exported-png-file.png"));

//Rendering a Buffered Image to an OutputStream
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ImageIO.write(bufferedImage,"png",bout);

Putting It All Together: Rendering Components to the Browser

Although we now know how to transform Swing components into images, we still need to put this knowledge to good use and render the images to the browser. In this article, we will be using servlets for drawing these dynamic images. It is, however, also worth noting that if your application is based on Java Server Faces, it is also possible to render these images using a PhaseListener. The PhaseListener image renderer is a useful approach when you are developing a third-party Java Server Faces library, as your end users would not be required to change their web.xml configuration file in order to use your components. The servlet approach, however, is a lot more familiar and also easier to get started with.

The SwingButtonServlet

A common requirement in many web applications is to have image buttons with "mouse over" effects. If these are done in the standard way, as static images, it can quickly become very tedious to maintain both a normal and an "active" (mouse over) version of each of the buttons. You also start running into problems when their labels need to change or if the application needs to be translated into a different language.

Instead, we will be drawing our image buttons dynamically using a servlet that simply picks up the label of the button from an URL parameter. With the buttons being drawn dynamically and from a central place, it becomes straightforward to change the "theme" of all of the buttons at once or to drive the button labels from a resource bundle/database.

Below is an excerpt of the JSP code that you can use to take advantage of these buttons--note how we have utilized a resource bundle to dynamically assign the labels.

...
<%-- Tag library and resource bundle definition--%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:setBundle basename="swing.Labels" var="bundle"/>
...
<input
  type="image"
  src="swingButton.png?title=>fmt:message key="login.button" bundle="${bundle}"/>"
  onmouseout="this.setAttribute('src','swingButton.png?title=<fmt:message key="login.button" bundle="${bundle}"/>');"
  onmouseover="this.setAttribute('src','swingButton.png?active=true&title=<fmt:message key="login.button" bundle="${bundle}"/>');"/>

In order to draw these buttons' images on the server side, we need to take the following steps:

  • Set the appropriate Swing look and feel. The Substance look and feel is currently one of the best-designed around and is freely available under an open source license; a perfect fit for our example.
  • Create the button and determine whether it is in the active (selected) state or not. This will be determined by whether or not the "active" parameter has been passed through on the URL request. In our example, the active (selected) state of the button will be used as the "hover over" image.
  • Convert the Swing button into an image using our SwingImageCreator utility class.
  • Render the image to the browser using the ImageIO library.

Here is the code that accomplishes the above:

..
public class SwingButtonServlet extends HttpServlet {  
   @Override
   public void init(ServletConfig config)
                      throws ServletException {
     try {
         UIManager.setLookAndFeel(new SubstanceLookAndFeel());
      } catch (UnsupportedLookAndFeelException e) {
         throw new ServletException("Could look and feel",e);
      }
   }
  
   @Override
   public void doGet(HttpServletRequest request,
                     HttpServletResponse response)
                         throws ServletException, IOException {

      //See if we can draw the button.
      byte[] imageData = drawSwingButton(request);
     
      //If it is null, then there is nothing more we can do
      if(imageData == null){
         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
         return;
      }
     
      response.setContentType("image/png");
      response.setContentLength(imageData.length);
      response.getOutputStream().write(imageData);
      response.setStatus(HttpServletResponse.SC_OK);
   }
  
   public byte[] drawSwingButton(HttpServletRequest request){
     
      //Check whether the button should be drawn in the active state
      boolean active = request.getParameter("active") != null;
      //Create the button with the 'title' parameter as it's label
      String title = request.getParameter("title");
      JToggleButton imageButton = new JToggleButton(title,active);
      imageButton.setSize(imageButton.getPreferredSize());
     
      //Now we need to create a byte array of this image
      //possibly for caching purposes etc.
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      BufferedImage img = SwingImageCreator.createImage(imageButton);
      try {
         ImageIO.write(img,"png",bos);
      } catch (IOException e) {
         //Should not really happen.
         return null;
      }
      return bos.toByteArray();
   }
}

Figure 4 shows the final result of our labors: image buttons being drawn on the fly.

Image buttons being drawn on the fly
Figure 4. Image buttons being drawn on the fly

Drawing More Complex Components

The technique we have just used are not limited to only Swing components available in the standard Java libraries. It is also possible to draw more complex components by using third-party libraries. In Figure 5, we used the popular JGraph library to draw a hierarchical organizational diagram:

A More Complex Example - Representing Hierarchical Data
Figure 5. A more complex example: representing hierarchical data

The basic recipe for drawing the above remains the same: build the component in memory and then render it to the browser as an image. The code for this GraphServlet is in fact shorter than what was required in the SwingButtonServlet. The reason for this is that building up a hierarchical data structure is a complex task that normally occurs as part of the business logic of your application (and is slightly beyond the scope of this article). However, you can download the full source code for this article from the link in the Resources section, should you want to investigate the OrganizationGraphModel further and see how this is done.

...
public class GraphServlet extends HttpServlet {

   private JGraphLayoutAlgorithm graphLayout;
  
   @Override
   public void init(ServletConfig config)
                                        throws ServletException {
      SugiyamaLayoutAlgorithm layout =  new SugiyamaLayoutAlgorithm();
      layout.setSpacing(new Point(170,120));
      graphLayout = layout;
   }

   @Override
   public void doGet(HttpServletRequest request,
                                         HttpServletResponse response)
                                        throws ServletException,
                           IOException {
     
      JGraph graph = new JGraph();
      graph.setBackground(Color.white);
      graph.setEditable(false);
      graph.setGridVisible(false);
     
      //Create the graph model.
      //In a real application you would probably get the model
      //from the session where it was set by something like
      //a JSF bean / Struts Action
      GraphModel model = new OrganizationGraphModel();
     
      graph.setModel(model);
      //Layout the graph
      graphLayout.run(graph,graph.getRoots());
      graph.clearSelection();
     
      //Now render the graph back to the user
      BufferedImage graphImage = SwingImageCreator.createImage(graph);
     
      //Now write the content back to the user.
      response.setContentType("image/png");
      OutputStream ous = response.getOutputStream();
      ImageIO.write(graphImage,"png",ous);
   }
}

Animated Images

So far, all of the images that we have drawn have been static. The next logical step is to make these images dynamically update themselves and appear to be "animated" from an end user's point of view.

Although this is not achievable using pure HTML, it is possible to implement it using a small amount of JavaScript. The JavaScript will basically instruct the browser to continually poll the server for the latest version of the image. To ensure that the browser does not cache the image, a unique URL parameter is passed to the server on each request:

<img id="dynamicImage" src="jxgraph.png"/>
<script>
function reloadImage(){
  var img = document.getElementById('dynamicImage');
  //Set a random parameter to ensure that the
  //browser does not cache the image
  img.setAttribute('src',"jxgraph.png?randomParam=" + Math.random());
  setTimeout('reloadImage();',1000);
}
setTimeout('reloadImage();',1000);
</script>

We need to ensure that the server draws a new image every time it is invoked. In the servlet example below, we will simply use an attribute in the user's session (HttpSession) to keep track of the previous request and ensure that the new component is different. The component we are using--JXGraph--has only recently been contributed to SwingLabs, though it is already clear that there is a huge amount of applications that can benefit from its features.

...
public class JXGraphServlet extends HttpServlet {

   @Override
   protected void doGet(HttpServletRequest request,
                        HttpServletResponse response)
                         throws ServletException, IOException {
     
      JXGraph graph = new JXGraph();
     
      HttpSession session = request.getSession();
      //Get the previous value from the session
      Double previousValue = (Double)session.getAttribute("previousValue");
      if(previousValue == null){
         previousValue = 0.1;
      }
     
      final double prevValue = previousValue.doubleValue();
     
      graph.addPlots(Color.RED, new JXGraph.Plot() {
         public double compute(double value) {
            return Math.sin((value * 6)- prevValue);
         }
      });

      session.setAttribute("previousValue",(prevValue + 0.1));
     
      //Now render the graph back to the user
      BufferedImage graphImage = SwingImageCreator.createImage(graph);
     
      //Now write the content back to the user.
      response.setContentType("image/png");
      OutputStream ous = response.getOutputStream();
      ImageIO.write(graphImage,"png",ous);
   }
}

Figure 6 shows one frame of this animated graph as seen in the browser.

The Final Result - A Dynamic (Animated) Image
Figure 6. The final result: a dynamic (animated) image

Ideas for the Future

We have only covered the tip of the iceberg in terms of what is possible with Swing on the server side. For instance, by adding in a bit more logic around the rendering of the components, it would be possible to calculate the location of each of the elements (i.e., the "CEO" box in our organizational chart example in Figure 5) on the image. These locations can then be used to create an HTML image map, which can add additional interactivity in the browser.

Despite the various AJAX frameworks available on the Web today, there still seems to be no easy way to draw images using JavaScript. The best JavaScript client-side solution currently available seems to be the HTML Canvas offered by Mozilla. This solution suffers from the drawback that it is non-standard and will probably not be supported by the other major browsers for some time to come.

By combining JavaScript on the client side with the rendering of Swing components on the server side, it would be possible to overcome these problems. A JavaScript component could, for instance, compute and send a request to the server specifying the exact type of component image it requires. The result would be a library that would be a thin client with a number of advanced Swing components and compatibility across a number of different browsers. The only real rival would be an actual desktop Swing application.

Conclusion

If you were take only one thing away from reading this article, then I would hope that it would be the realization that Swing is a technology that is not only useful in desktop applications, but also in enterprise/web development. In this article, I offered Java EE developers some insights on how it is possible to take advantage of the huge strides made by Swing in recent years and to use its advanced "desktop" components in enterprise/web applications.

Resources

  • Source code for this article. Please refer to the dependencies.txt file for details of the JARs you require.
  • The Substance look and feel: Used to draw the buttons.
  • The JGraph library: Used to draw the hierarchical organizational diagram.
  • SwingLabs: The home of various useful Swing components, including the JXGraph component used in this article.

width="1" height="1" border="0" alt=" " />
Jacobus Steenkamp has been developing in Java for the last 4.5 years and has worked in various sectors including the financial, pharmaceutical and energy industries.
Related Topics >> Swing   |   Web Design   |