The Cold Fusion Web Database Construction Kit

Previous chapterNext chapterContents


- 28 -
The Database Component Framework



David Watts

No book in the last six months that has anything to do with the Internet would be complete without a mention of Java. This book won't be any different! If you're overwhelmed by the hype surrounding Java, or think it's just for adding cute little animated effects to your Web pages, you're in for a pleasant surprise. In this chapter, you'll learn that Java can play a useful role in your Web development in coordination with Cold Fusion.

Fusing Your Data with Java and Cold Fusion

You've probably seen plenty of Web pages that use Java to provide multi-media elements. If you're like most business-oriented developers, you might have been impressed with the visual effects and the handling of graphics, but haven't found a place for Java to provide useful content to your pages. The makers of Cold Fusion liked the way Java could display graphics, so they developed a connection between Java and Cold Fusion to easily display dynamically created charts and graphs retrieved by Cold Fusion. In this section, you will read a little background information about Java and learn about the connection between Java and Cold Fusion.

A Brief Explanation of Java

What exactly is Java? An outside observer might have a little trouble answering this question because of all the hype surrounding Java. Java is just a programming language and a set of specifications for a runtime environment. Java applications write in a language similar to C++, compile into a nonmachine-specific bytecode format, and execute within a runtime environment known as a virtual machine.

Simply put, you can develop Java applications that run on any platform that supports a Java runtime environment. You don't have to worry about platform-specific issues, APIs, or screen layouts. Your Java applications operate the same on any of these platforms, and you don't have to create different compiles, or maintain separate sources, or any of the other problems normally associated with multi-platform development.

The makers of Java originally intended it only as a language for small, embedded processors, but some features of Java make it a programming language for the Web. Java's runtime environment allows for security settings that prevent untrusted code from seriously harming the system, and provides verification of Java bytecodes before execution to ensure the safety of that code. This runtime environment is small and can easily be included as part of a browser. Netscape Navigator 2 and Microsoft Internet Explorer 3 contain a Java runtime, and the latest releases of Windows NT and Windows 95 from Microsoft, and IBM's OS/2, will most likely contain a Java runtime. Other browsers and operating systems will probably follow.

Most important, Java provides a mechanism for developing applications that can be executed from a Web page. These applications are called applets. Applets work in concert with the browser and the Java runtime to display within a Web page or even within a separate frame on the desktop. Applets communicate only with the server from which they are called and can send and retrieve data between the desktop and the server.

This is, of course, a very brief and incomplete explanation of Java. If you want to know more, pick up one of the many available books about Java, such as Que's Special Edition Using Java.

Using Java Applets on Your Web Pages

Java applets can add graphics and effects to your Web pages as well as display meaningful data from your databases. Several ways can accomplish this that vary in complexity and results. The easiest way is to simply include existing applets within your pages and generate dynamic parameters for them by using Cold Fusion. This doesn't require that you know anything about DCF or Java development because you only need to include an applet within your pages. To do this, you need the applet and its list of parameters.

Here's a simple example, using one of the demonstration applets that come with the Java Development Kit from Sun Microsystems, the original developers of Java. The Java Development Kit is free and can be found on the CD.

The first step is to place the applet (consisting of one or more Java class files) somewhere within your visible document tree. In this example, the file Chart.class is in the /java/ directory of www.a2z.com.

You want to call the applet from within a Web page by using the APPLET tag. Listing 28.1 shows the example HTML file with the APPLET tag and a list of parameters within it.


TIP: You can place HTML text and graphics within an APPLET tag that will only appear if viewed with a browser that isn't Java-enabled.

Listing 28.1  BARGRAPH.HTML--A Sample HTML File Containing an APPLET Tag That Is a Container Tag with Parameter Tags Within It

<HTML>
<HEAD>
<TITLE>Bar Chart</TITLE>
</HEAD>
<BODY>
<HR>
<APPLET CODE="Chart.class" WIDTH=251 HEIGHT=125>
<PARAM NAME=c1 VALUE="10">
<PARAM NAME=c2 VALUE="20">
<PARAM NAME=c3 VALUE="5">
<PARAM NAME=c4 VALUE="30">
<PARAM NAME=c1_label VALUE="Q1">
<PARAM NAME=c2_label VALUE="Q2">
<PARAM NAME=c3_label VALUE="Q3">
<PARAM NAME=c4_label VALUE="Q4">
<PARAM NAME=c1_color VALUE="blue">
<PARAM NAME=c2_color VALUE="green">
<PARAM NAME=c3_color VALUE="magenta">
<PARAM NAME=c4_color VALUE="yellow">
<PARAM NAME=c1_style VALUE="striped">
<PARAM NAME=c2_style VALUE="solid">
<PARAM NAME=c3_style VALUE="striped">
<PARAM NAME=c4_style VALUE="solid">
<PARAM NAME=title VALUE="Performance">
<PARAM NAME=columns VALUE="4">
<PARAM NAME=orientation VALUE="horizontal">
<PARAM NAME=scale value="5">
</APPLET>
<HR>
</BODY>
</HTML>


CAUTION: Java is a case-sensitive language, so be sure to specify exactly the name and parameters of the applet. You must also make sure that all the class files used by a specific applet are in their correct locations and are properly referenced in your APPLET CODE and CODEBASE values. In addition, the name of the class you are calling must match the case with the name of the class file. For example, the class file barchart.class,as installed by Cold Fusion, must be referenced in your APPLET CODE value as "BarChart.class"; otherwise you will receive a java.lang.ClassFormatError error message.

Figure 28.1 shows the HTML file and its applet displayed within the browser.

Figure 28.1  The barchart from Listing 28.1 displayed in Netscape.

With a little modification, we can adapt this applet to display data retrieved by Cold Fusion. Figure 28.2 shows a template that performs a query of book inventory and then generates a bar chart. The code shown in Listing 28.2 generates parameters for the applet by using Cold Fusion.

Listing 28.2  BARGRAPH.CFM--This Cold Fusion Template Uses Query Output to Generate Parameters for the Chart Applet

<CFQUERY NAME="BookGraphInfo" DATASOURCE="A2Z Books">
     SELECT Title, NumberInStock FROM Inventory
</CFQUERY>
<HTML>
<HEAD>
<TITLE>A2Z Simple Bar Graph of Inventory</TITLE>
</HEAD>
<BODY>
<APPLET CODE="Chart.class" width=500 height=125>
<PARAM NAME=title VALUE="Inventory">
<PARAM NAME=orientation VALUE="horizontal">
<PARAM NAME=scale VALUE="5">
<CFSET #LoopCount# = 1>
<CFSET #Color# = "red">
<CFOUTPUT QUERY="BookGraphInfo">
     <PARAM NAME=c#LoopCount# VALUE="#BookGraphInfo.NumberInStock#">
     <PARAM NAME=c#LoopCount#_label VALUE="#BookGraphInfo.Title#">
     <PARAM NAME=c#LoopCount#_color VALUE="#Color#">
     <CFSET #LoopCount# = #LoopCount# + 1>
     <CFIF #Color# IS "red">
          <CFSET #Color# = "green">
     <CFELSEIF #Color# IS "green">
          <CFSET #Color# = "blue">
     <CFELSEIF #Color# IS "blue">
          <CFSET #Color# = "yellow">
     <CFELSEIF #Color# IS "yellow">
          <CFSET #Color# = "red">
     </CFIF>
</CFOUTPUT>
<CFSET #LoopCount# = #LoopCount# - 1>
<CFOUTPUT><PARAM NAME=columns VALUE="#LoopCount#"></CFOUTPUT>
</APPLET>
</BODY>
</HTML>

Figure 28.2  The bar graph generated by parameters set by using Cold Fusion.

The DCF--A Bridge Between Java and Cold Fusion

As you've seen, you can easily use an existing Java applet to display data from Cold Fusion queries. It is a little difficult to configure and is just a static display. The next step is to use the Cold Fusion Graphlets provided in the Cold Fusion package by Allaire. By using the DCF interface, these applets display static data and can refresh this data repeatedly after a user-specified interval. In addition, you can easily write your APPLET tags because Graphlets accept lists of parameters that can be easily generated and arranged within your tags by using the ValueList formatting function.

The DCF interface is a set of Java classes, provided with Cold Fusion, that contains methods to retrieve formatted data from URLs. You don't have to know anything about DCF or Java programming to use the Graphlets, and you'll learn more about DCF itself later.

Using the Allaire DCF Graphlets

Allaire has made it easy for you to use the DCF. Seven DCF Graphlets and the DCF classes install onto your server when you install Cold Fusion. You only need to start using the Graphlets in your Web pages. This section covers the steps you need to perform to use Graphlets.

Configuring Your Server

The Cold Fusion installation creates a directory with a subdirectory in the root of your document tree, /Classes/CFGraphs/, that contains another subdirectory, /Allaire/, as well as seven Java class files. Six of these files are your Graphlets. Within the /Allaire/ subdirectory are the Java class files that make up the Database Component Framework.

Once you install Cold Fusion, the Java files are ready to run. If your server is multihoming, however, you will need to map the /Classes/ directory to each server with which you want to use Graphlets.


CAUTION: If you move your Graphlets to a different location, make sure that you maintain the exact sub-directory structure found within the CFGraphs directory. The Allaire subdirectory contains the Allaire DCF package. Packages in Java are groups of related classes, and the directory structure matches the class hierarchy within the package.

General Graphlet Use

All APPLET tags share some characteristics. The first line of a typical APPLET tag looks like this:

<APPLET CODE="BarChart.class" CODEBASE="/Classes/CFGraphs/" WIDTH=250 HEIGHT=100>

This line contains four parameters that must be specified. CODE is the name of the specific Java applet and is case-sensitive. CODEBASE is the directory in which the applet is located and is also case-specific. Notice that the directory is the default installation directory. WIDTH and HEIGHT are measured in pixels and specify the size of the applet within the page.

The last line is the closing tag, </APPLET>.

You place parameter tags between these two APPLET tags.

Some of the parameters you can set, whereas some parameters you must set (like the ones used for the initial loading of data). Let's cover those first.


CAUTION: Remember that parameter names are case-sensitive. ChartData.Columns is different from CHARTDATA.COLUMNS. The Graphlet will try, in the absence of the expected parameters, to match uppercase and lowercase variants, but don't expect it to work as well as if you had correctly specified the parameters.

Data List Parameters. Data parameters for Graphlets are named ChartData.ParameterName and take comma-delimited lists for their values.

The first data parameter, ChartData.Columns, sets the number and names of other data parameters. The following line shows this parameter set to list two other parameters, Items and Values:

<PARAM NAME="ChartData.Columns" VALUE="Items,Values">

You can then include those parameters that you've named. The following lines show parameters for ChartData.Items and ChartData.Values that contain the months in the year and the number of days in each month:

<PARAM NAME="ChartData.Items" VALUE="Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec">
<PARAM NAME="ChartData.Values" VALUE="31,28,31,30,31,30,31,31,30,31,30,31">

You can also specify colors for each item within your graph by using the ChartData.Colors parameter:

<PARAM NAME="ChartData.Colors" VALUE="red,green,blue,yellow,red,green,blue,yellow,red,green,blue,yellow">

This parameter takes any of these colors: red, blue, green, yellow, magenta, cyan, orange, pink, and dark gray. If you don't specify this parameter, the applets display your graph items in these colors in the order listed above, one for each item in the graph, until the last color is reached. If your graph has more than nine items, additional items will display by using dark gray. If you use this parameter and have more items in your graph than you have colors specified, they also display by using dark gray.

If you include the ChartData.Colors parameter, don't forget to include it in your ChartData.Columns:

<PARAM NAME="ChartData.Columns" VALUE="Items,Values,Colors">

Dynamic Data Population of Parameters. You've seen how to use Graphlets with static data. Now you'll see how Cold Fusion can dynamically generate comma-delimited lists by using the ValueList function to fill the data parameters.

Listing 28.3 shows part of a Cold Fusion template that queries the A2Z Books database for book ISBN and number of copies in stock, and then generates value lists for the ChartData.Items and ChartData.Values parameters.

Listing 28.3  DCFBAR.CFM--This Template Uses Cold Fusion to Generate Value Lists for Parameters

<CFQUERY NAME="BookGraphInfo" DATASOURCE="A2Z Books">
     SELECT ISBN, NumberInStock FROM Inventory
</CFQUERY>
<APPLET CODE="BarChart.class" CODEBASE="/Classes/CFGraphs/" WIDTH=500 height=250>
<CFOUTPUT>
<PARAM NAME="ChartData.Columns" VALUE="Items,Values">
<PARAM NAME="ChartData.Items" VALUE="#ValueList(BookGraphInfo.ISBN)#">
<PARAM NAME="ChartData.Values" VALUE="#ValueList(BookGraphInfo.NumberInStock)#">
</CFOUTPUT>
</APPLET>

Data Refresh Parameters. You can do anything you did with the Graphlets with any applet that accepts parameters (see Listing 28.1), but one difference makes Graphlets better.

The essential difference between Graphlets and regular Java applets is that Graphlets can call a Cold Fusion template and refresh its data display with two parameters: RefreshTime and RefreshDataFromURL. The RefreshTime parameter is the time, in seconds, that the Graphlet should wait before it retrieves data. By default, this value is zero and does not refresh the data. The RefreshDataFromURL parameter specifies a fully qualified Cold Fusion template URL from which the graphlet gets its data. The following example refreshes data every minute from a template named currentstock.cfm:

<PARAM NAME="RefreshTime" VALUE="60">
<PARAM NAME="RefreshDataFromURL" VALUE="http://www.a2z.com/a2z/java/currentstock.cfm">

The template used to refresh data should contain only the CFQUERY to retrieve the data, labels for the output columns, and the output columns themselves:

<CFQUERY NAME="CurrentStock" DATASOURCE="A2Z Books">
     SELECT ISBN, NumberInStock FROM Inventory
</CFQUERY>
Columns:Items,Values
<CFOUTPUT QUERY="CurrentStock">
#ISBN#,#NumberInStock#
</CFOUTPUT>

The RefreshTime parameter from the original APPLET tag is added to the URL request made by the applet, so you can use it in this template.


Optional Parameters. All the Graphlets accept the optional parameters listed in Table 28.1. In addition, different Graphlets have additional optional parameters that are listed later.

Table 28.1  Optional Parameters for All Graphlets

Parameter Description Default Value
Title Chart title Chart
TitleFontName Font for the chart title Times Roman
TitleFontHeight Font size for the chart title 12
DrawBorders Outlines around bars or slices by using No the font color
ShowDateTime Date and time display below chart title Yes
BackgroundColor Hexadecimal color value C0C0C0 for background
FontColor Hexadecimal color value for font 000000 and borders
GridLineColor Hexadecimal color value for grid 808080 lines and axes descriptions

The TitleFontName refers to fonts available to Java rather than native system font types such as TrueType or Adobe Type Manager fonts. These fonts depend on the implementation of the Java virtual machine, so some fonts are available on some systems but not others. The three most commonly found are TimesRoman (without a space between the two words), Helvetica and Courier.

The ShowDateTime parameter is fixed format, so you can't customize the display of the date and time.

If you provide a color value by using one of the color string names commonly used by Java, the Graphlet fails to load, producing a java.lang.NumberFormatException error.

Graphlet Types

All of the previous information applies to all types of Graphlets; this section deals with the differences between their optional parameters. The Graphlets can be divided into two categories. The first, which includes bar charts and pie charts, are suited to displaying simple data that could be represented within a two-dimensional array structure of items and values, like a spreadsheet. The second, which includes area, line, multibar and 3-D multibar charts, allows you to add a third dimension of groups to items and values.


Bar Chart and Pie Chart. Bar charts and pie charts display two-dimensional data arrays in bars of different lengths or as slices of a circular pie. The optional parameters accepted by these applets are listed in Table 28.2.

Table 28.2  Optional Parameters

Parameter Description Default Value Used By
ShowLegend Display a legend for the chart Yes Pie Chart
Orientation Horizontal or vertical bar Vertical Bar Chart orientation
Shadow Number of pixels used to 4 Bar Chart display shadow effect

Area, Line, Multibar, and 3-D Multibar Charts. These four charts display multi- dimensional data arrays, which can be used to display data in more than the two dimensions used by the pie and bar charts. The optional parameters used by these applets are listed in Table 28.3.

Table 28.3  Optional Parameters

Parameter Description Default Value Used By
Cumulative Cumulate the data No Area Chart, Line Chart
DotSize Size of the dots in pixels 2 Line Chart
Rotation Angle rotation from 0 to 90 degrees 10 3-D Multibar Chart
Elevation Angle of elevation from 0 to 90 degrees 30 3-D Multibar Chart
Distance Viewing distance in graph widths 3 3-D Multibar Chart

Example of a Refreshing Graphlet

Now that you've seen what makes up a Graphlet, we'll build an example HTML page, using frames, that displays a dynamically updating Graphlet. Because no one else will be updating your sample database, you'll update it yourself from the lower frame. Figure 28.3 shows what it looks like when first shown.

Figure 28.3  The first display of the chart uses data specified as applet parameters.

Listing 28.4 shows the HTML frame that contains the document with the applet and the document used to modify the database.

Listing 28.4  DYNAMICCHART.HTML--A Simple Frame Document Containing Our Applet Document

<HTML>
<HEAD>
<TITLE>A2Z Dynamic Chart</TITLE>
</HEAD>
<FRAMESET ROWS="80%,20%">
     <FRAME NAME="chart" SRC="chart.cfm">
     <FRAME NAME="controls" SRC="controls.cfm">
</FRAMESET>
</HTML>

Next, the actual document that invokes the applet is in Listing 28.5. Notice that it contains the RefreshDataFromURL, which must contain a fully qualified URL, and RefreshTime.

Listing 28.5  CHART.CFM--The Contents of the Chart Applet Document

<CFQUERY NAME="GetBookInfo" DATASOURCE="A2Z Books">
     SELECT ISBN, NumberInStock FROM Inventory
</CFQUERY>
<HTML>
<HEAD>
<TITLE>Chart Applet</TITLE>
</HEAD>
<BODY>
<APPLET CODE="PieChart.class" CODEBASE="/Classes/CFGraphs/" WIDTH="450" HEIGHT="250">
<!--- Required data parameters used to start the Graphlet --->
<CFOUTPUT>
<PARAM NAME="ChartData.Columns" VALUE="Items,Values">
<PARAM NAME="ChartData.Items" VALUE="#ValueList(GetBookInfo.ISBN)#">
<PARAM NAME="ChartData.Values" VALUE="#ValueList(GetBookInfo.NumberInStock)#">
</CFOUTPUT>
<!--- Optional display parameters --->
<PARAM NAME="Title" VALUE="Books In Stock">
<!--- Refresh parameters --->
<PARAM NAME="RefreshTime" VALUE="2">
<PARAM NAME="RefreshDataFromURL" VALUE="http://www.a2z.com/ch28/dynamic/refresh.cfm">
<!--- Display something to non-Java-capable browsers --->
<H1>Your browser does not support Java!</H1>
Download a new one from Netscape or Microsoft!<BR>
</APPLET>
</BODY>
</HTML>

We'll use a template to select the record for the book with the least number of copies in stock, and then permit the user to change this value in a form (see Listing 28.6).

Listing 28.6  CONTROLS.CFM--This Template Selects the Book with the
Fewest Copies in Stock by Using the Aggregate Function MIN

<CFQUERY NAME="GetBookInfo" DATASOURCE="A2Z Books">
     SELECT * FROM Inventory
     WHERE NumberInStock = 
     (SELECT MIN(NumberInStock)
     FROM Inventory)
</CFQUERY>
<HTML>
<HEAD>
<TITLE>Chart Updater</TITLE>
</HEAD>
<BODY>
<FORM ACTION="change.cfm" METHOD="POST">
<CFOUTPUT QUERY="GetBookInfo">
Current number in stock: #NumberInStock#
<INPUT TYPE="hidden" NAME="BookID" VALUE="#BookID#">
<INPUT TYPE="hidden" NAME="CategoryID" VALUE="#CategoryID#">
<INPUT TYPE="hidden" NAME="Title" VALUE="#Title#">
<INPUT TYPE="hidden" NAME="Publisher" VALUE="#Publisher#">
<INPUT TYPE="hidden" NAME="PublicationDate" VALUE="#PublicationDate#">
<INPUT TYPE="hidden" NAME="AuthorFirstName" VALUE="#AuthorFirstName#">
<INPUT TYPE="hidden" NAME="AuthorLastName" VALUE="#AuthorLastName#">
<INPUT TYPE="hidden" NAME="Pages" VALUE="#Pages#">
<INPUT TYPE="hidden" NAME="Description" VALUE="#Description#">
<INPUT TYPE="hidden" NAME="DueDate" VALUE="#DueDate#">
<INPUT TYPE="hidden" NAME="Location" VALUE="#Location#">
</CFOUTPUT>
<INPUT TYPE="text" NAME="NumberInStock">
<INPUT TYPE="submit">
</BODY>
</HTML>

After this form has been submitted, it launches the CFUPDATE tag in change.cfm (see Listing 28.7).

Listing 28.7  CHANGE.CFM--This Updates the "Inventory" Table

<CFUPDATE DATASOURCE="A2Z Books" TABLENAME="Inventory">
<HTML>
<HEAD>
<TITLE>Chart Updater</TITLE>
</HEAD>
<BODY>
<A HREF="controls.cfm"><H2>Chart data has been updated!</H2></A>
</BODY>
</HTML>

The Graphlet that is executing refresh.cfm every two seconds retrieves the new data. See Listing 28.8 for refresh.cfm.

Listing 28.8  REFRESH.CFM--This Template Is Used by the Graphlet to Retrieve Data Updates

<CFQUERY NAME="GetBookInfo" DATASOURCE="A2Z Books">
     SELECT ISBN, NumberInStock FROM Inventory
</CFQUERY>
Columns: Items,Values
<CFOUTPUT QUERY="GetBookInfo">
#ISBN#,#NumberInStock#
</CFOUTPUT>

Figure 28.4 shows the result of the update.

Figure 28.4  The updated graph.

Developing Java Applets by Using DCF

You've seen how to use the DCF Graphlets that Allaire provides. But the DCF can also be used to Cold-Fusion-enable Java applets that you develop yourself. This may be more than you want to get into, since Java is a relatively complex development language; but if you're willing to invest the time and effort in learning Java, you can easily roll your own DCF applets.

Using the Java Language

It is beyond the scope of this book to introduce you to Java programming, which is comparable in syntax and complexity to C++. Instead, we will assume a little bit of Java knowledge in this section and provide a simple description of the Database Component Framework.

What you will need, then, to use this section is a Java development environment and a little background knowledge of Java. You can use the freeware Java Development Kit from Sun, available for download from http://www.javasoft.com. Or, you can use one of the many Java development environments available now, such as Symantec Café, Sun's Java Workshop, or Microsoft Visual J++.

Once you have this installed, you will also have to include the Allaire DCF classes within your classpath. The classpath is the path that the Java development environment uses to find classes included in the classes you compile. The DCF classes themselves are installed in the /Classes/CFGraphs/Allaire/ directory within your Web server's document tree. You can either include this directory in your classpath, or you can copy the Allaire directory to your existing Java library root directory. Check your Java development environment's documentation for more details on setting the classpath.

Building a Simple DCF Applet

For this example, we will build a very simple applet, possibly the simplest DCF applet you can make. Our applet will not perform any complex graphing, like the Allaire DCF Graphlets, but instead will display two fields, ISBN and NumberInStock, from our Inventory database. The ISBN number will be listed in a choice box, which is the Java name for a drop-down box; and when the user selects an entry from this choice box, the matching NumberInStock value will be displayed to the right by using a label control.

Most of Listing 28.9 is typical applet code to handle starting, stopping, and refreshing details. The part you want to read carefully is the retrieveData method, which uses the DCF classes to retrieve data from the template that calls the applet and from a refresh template, if one exists.

Listing 28.9  DCFDEMO.JAVA--Java Code for Our Simple DCF Applet

// Import the java classes for this applet
import java.awt.*;
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.io.*;
import java.lang.*;
import java.net.URL;
// Import the DCF classes
import allaire.dcf.recordset.*;
import allaire.util.*;
public class DCFDemo extends Applet implements Runnable {
    // This thread is used to run the applet
    Thread      runner;
    // These variables are for use with the DCF.
    // The Recordset and Query instances come
    // from the DCF classes of the same name.
    // The refreshTime and debugInfoEnabled
    // are used to set DCF-specific parameters.
    Recordset   formData;
    Query       query;
    int         refreshTime = 0;
    boolean     debugInfoEnabled = true;
    // These variables are used for the data
    // retrieved using the DCF.
    String[]    ISBN;
    String[]    numberInStock;
    int         columns;
    // These variables are used to display the data
    Label labelISBN;
    Label labelInStock;
    Choice choiceISBN;
    Label displayNumberInStock;
    // standard construction start, stop, run for applet
    // running as thread
    public void start() {
      if (runner == null) {
         runner = new Thread(this);
         runner.start();
      }
    }
    public void stop() {
      if (runner != null) {
         runner.stop();
         runner = null;
      }
    }
    public void run() {
      while (true) {
         if ( refreshTime == 0 ) {
            // stop thread if no refresh required
            stop();
            return;
         }
         pause(refreshTime * 1000);
         // refresh data and repaint applet
         this.retrieveData();
         repaint();
      }
    }
    private void pause(int time) {
      try { Thread.sleep(time); }
      catch (InterruptedException e) {
        if ( debugInfoEnabled ) Debug.write(e.getMessage());
      }
    }
    // The init method creates the screen layout,
    // and calls the data retrieval method.
    public synchronized void init() {
      super.init();
      setLayout(new FlowLayout());
      resize(300,200);
      labelISBN = new Label("ISBN:");
      add(labelISBN);
      labelISBN.reshape(10,10,50,20);
      choiceISBN = new Choice();
      add(choiceISBN);
      labelInStock = new Label("Number In Stock:");
      add(labelInStock);
      displayNumberInStock = new Label();
      add(displayNumberInStock);
      // This section retrieves the refresh time and URL, if any.
      // If they exist, the applet will append the refresh time to
      // the query URL when it is called, so that you can read that
      // in your refresh query template if you want.
      String rs;
      rs = getParameter("RefreshTime");
      if (rs == null) {
          refreshTime = 0;
      } else {
          refreshTime = Integer.parseInt(rs);
          rs = getParameter("RefreshDataFromURL");
          if (rs == null) {
             // if no URL selected - do not refresh
             refreshTime = 0;
          } else {
             // query definition
             query = new Query(rs);
             // add refreshTime parameter to the URL
             query.addParam( "RefreshTime", String.valueOf(refreshTime) );
          }
        }	
      this.retrieveData();
    }
    // Here is the most important method in this applet.
    // This method retrieves data from a Cold Fusion query
    // using instances of the DCF Query and Recordset classes.
    private void retrieveData() {
        try {
            // This decision tree either retrieves data from the
            // initial applet parameters, or retrieves it from a
            // refresh template.
            if ( formData == null ) {
                formData = new AppletParamRecordset(this);
            } else {
                query.execute();
                formData = query.getRecordset();
            }
            columns = formData.getRowCount();
            ISBN = new String[columns];
            numberInStock = new String[columns];
            // This for loop retrieves the data from the comma-delimited
            // lists in the template, and adds ISBN numbers to the choice
            // box.
            for (int i = 0; i < columns; i++) {
                ISBN[i] = formData.getData(i + 1, "ISBN");
                numberInStock[i] = formData.getData(i + 1, "NumberInStock");
                choiceISBN.addItem(ISBN[i]);
            }
            displayNumberInStock.setText(numberInStock[0]);
        } catch (Exception e) {
            if (debugInfoEnabled) Debug.write(e.getMessage());
        }
    }
    // This event method is triggered by selecting an entry from the
    // choice box.
    public void selectedChoiceISBN() {
        int selectedISBNIndex = choiceISBN.getSelectedIndex();
        displayNumberInStock.setText(numberInStock[selectedISBNIndex]);
    }
    public boolean handleEvent(Event event) {
        if (event.id == Event.ACTION_EVENT && event.target == choiceISBN) {
            selectedChoiceISBN();
            return true;
        }
    return super.handleEvent(event);
    }
}

First, we'll examine the init method, which is used to prepare the applet for display. In the case of a DCF applet, this also means retrieving parameters and initial data sets. Our simple applet is retrieving only two regular parameters, and it uses the standard getParameter method to do so. These parameters are RefreshTime and RefreshDataFromURL. If these parameters exist, they will be used for the query execution in the retrieveData method every time that method is called from the run method.

The part deserving most of your attention, the retrieveData method, is doing all the interesting stuff. The entire method is wrapped in a try/catch exception catcher, in case there is any problem retrieving data. Within the try portion, the first step is to determine whether the applet has previously read any data in, since this method is called every time the applet repaints, and then either read the initial data or execute the refresh query:

if (formData == null) {
                formData = new AppletParamRecordset(this);
            } else {
                query.execute();
                formData = query.getRecordset();
            }

The next step is to find out how many columns of data there are, and then create string arrays with enough space to hold all the columns:

columns = formData.getRowCount();
ISBN = new String[columns];
            numberInStock = new String[columns];

Finally, in this method you retrieve the data from the recordset into the array variables, and then fill the choice box. The last line shown here initializes the display label with the value for the first ISBN number:

for (int i = 0; i < columns; i++) {
                ISBN[i] = formData.getData(i + 1, "ISBN");
                numberInStock[i] = formData.getData(i + 1, "NumberInStock");
                choiceISBN.addItem(ISBN[i]);
            }
            displayNumberInStock.setText(numberInStock[0]);

Listing 28.10 shows the template you'll use to call this applet. It uses a query to get the initial data for the applet.

Listing 28.10  DCFDEMO.CFM--This Template Calls and Initializes the Applet

<CFQUERY NAME="DCFDemo" DATASOURCE="A2Z Books">
    SELECT ISBN, NumberInStock FROM Inventory
</CFQUERY>
<HTML>
<HEAD>
<TITLE>DCF Demo</TITLE>
</HEAD>
<BODY>
<APPLET CODE="DCFDemo.class" CODEBASE="/Classes/CFGraphs/" WIDTH="300" 
HEIGHT="300">
<PARAM NAME="columns" value="ISBN,NumberInStock">
<CFOUTPUT QUERY="DCFDemo">
<PARAM NAME="ISBN" VALUE="#ValueList(DCFDemo.ISBN)#">
<PARAM NAME="NumberInStock" VALUE="#ValueList(DCFDemo.NumberInStock)#">
</CFOUTPUT>
<PARAM NAME="RefreshTime" VALUE="0">
<PARAM NAME="RefreshDataFromURL" 
VALUE="http://www.a2z.com/ch31/DCFDemo/refresh.cfm">
</APPLET>
</BODY>
</HTML>

The refresh template contains the same query as the previous template, but outputs just the data as explained in the Graphlet section.

This has been a very simple introduction to DCF programming. If you're interested in learning more, a good idea would be to DCF-enable some of the sample applets that come with the Java Development Kit. Listing 28.11 shows a DCF-enabled version of the Chart applet from the JDK.

Listing 28.11  DCFCHART.JAVA--A Sample DCF-Enabled Chart Applet

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.io.*;
import java.lang.*;
import java.net.URL;
import allaire.dcf.recordset.*;
import allaire.util.*;
public class DCFChart extends java.applet.Applet implements Runnable {
    //*** the Runnable interface - applet can run as separate thread
    static final int	VERTICAL = 0;
    static final int 	HORIZONTAL = 1;
    static final int	SOLID = 0;
    static final int	STRIPED = 1;
    int			orientation;
    String		title;
    Font		titleFont;
    FontMetrics		titleFontMetrics;
    int			titleHeight = 15;
    int			columns;
    int			values[];
    Object		colors[];
 	Object		labels[];
    int			styles[];
    int			scale = 10;
    int			maxLabelWidth = 0;
    int			barWidth;
    int			barSpacing = 10;
    int			max = 0;
    // DCF declarations for recordset and query
    Recordset   chartData;
    Query       query;
    int         refreshTime;
    boolean     debugInfoEnabled;
    Thread      runner;
    public synchronized void init() {
    	// The return string variable used to get parameters
    	String rs;
        // Get the title parameter
    	titleFont = new java.awt.Font("Courier", Font.BOLD, 12);
    	titleFontMetrics = getFontMetrics(titleFont);
    	title = getParameter("title");
    	if (title == null) {
    	    title = "Chart";
    	}
        // Get the scale parameter
        rs = getParameter("scale");
    	if (rs == null) {
    	    scale = 10;
    	} else {
    	    scale = Integer.parseInt(rs);
    	}
        // Get the orientation parameter
    	rs = getParameter("orientation");
    	if (rs == null) {
    	    orientation = VERTICAL;
    	} else if (rs.toLowerCase().equals("vertical")) {
    	    orientation = VERTICAL;
    	} else if (rs.toLowerCase().equals("horizontal")) {
    	    orientation = HORIZONTAL;
    	} else {
    	    orientation = VERTICAL;
    	}
        // Get the data refresh parameters
        rs = getParameter("RefreshTime");
        if (rs == null) {
            refreshTime = 0;
        } else {
            refreshTime = Integer.parseInt(rs);
            rs = getParameter("RefreshDataFromURL");
            if (rs == null) {
                // if no URL selected - do not refresh
                refreshTime = 0;
            } else {
                // query definition
                query = new Query(rs);
                // add refreshTime parameter to the URL
                query.addParam( "RefreshTime", String.valueOf(refreshTime) );
            }
        }
       	// should the debug info window be enabled?
       	rs = getParameter("DebugInfoEnabled");
       	if (rs == null) {
       	   debugInfoEnabled = false;
          } else if ( rs.toLowerCase().equals("yes") ) {
       	   debugInfoEnabled = true;
          } else {
             debugInfoEnabled = false;
       	}
        // retrieve graph data from params
        this.getData();
    }
    // getData method retrieves data from applet params
    // or from refresh template
    private void getData() {
          int      i;
          String   rs;
          int      value;
          try {
             if ( chartData == null ) {
                // initial data come from the applet params ...
                chartData = new AppletParamRecordset(this, "ChartData");
             } else {
                // ... otherwise get data from refresh template
                query.execute();
                chartData = query.getRecordset();
             }
             // show info in the status bar
             if ( refreshTime == 0 ) {
                getAppletContext().showStatus("Done");
             } else {
                getAppletContext().showStatus("...");
             }
             columns = chartData.getRowCount();
    // set dimension for the arrays
             values = new int[columns];
      colors = new Color[columns];
             labels = new String[columns];
             styles = new int[columns];
             // scroll thru records
             for ( i = 0; i < columns; i++) {
        	    // retrieve style type or set default if not defined
                if ( !chartData.columnExists("Styles") ) {
            	    styles[i] = (chartData.getData ( i + 1, "Labels").toLowerCase().equals("striped"))
        	                    ? STRIPED : SOLID ;
        	    } else {
        	        styles[i] = SOLID;
        	    }
                // retrieve label
                labels[i] = chartData.getData ( i + 1, "Labels" );
       	        maxLabelWidth = Math.max(
       	            titleFontMetrics.stringWidth( chartData.getData ( i+1, "Labels" ) ),
       				   maxLabelWidth);
                // retrieve value
       		    try {
       		       value = Integer.parseInt( chartData.getData ( i+1, "Values" ));
                } catch (Exception e) {
                   value = 0;
                }
                values[i] = value;
                max = Math.max( max, value);
                // retrieve color
                if ( !chartData.columnExists("Colors") ) {
                   colors[i] = Color.black;
                } else {
                   rs = chartData.getData ( i + 1, "Colors" );
                   if ( rs.equals ( "red" )) {
       		            colors[i] = Color.red;
                   } else if (rs.equals ( "green" )) {
       		            colors[i] = Color.green;
                   } else if (rs.equals("blue")) {
       		            colors[i] = Color.blue;
                   } else if (rs.equals("pink")) {
       		            colors[i] = Color.pink;
                   } else if (rs.equals("orange")) {
       		            colors[i] = Color.orange;
                   } else if (rs.equals("magenta")) {
       		            colors[i] = Color.magenta;
                   } else if (rs.equals("cyan")) {
       		            colors[i] = Color.cyan;
                   } else if (rs.equals("white")) {
       		            colors[i] = Color.white;
                   } else if (rs.equals("yellow")) {
       		            colors[i] = Color.yellow;
                   } else if (rs.equals("gray")) {
       		            colors[i] = Color.gray;
                   } else if (rs.equals("darkGray")) {
       		            colors[i] = Color.darkGray;
                   } else {
       		            colors[i] = Color.black;
                   }
                }
             }
          } catch (Exception e) {
             if ( debugInfoEnabled ) Debug.write(e.getMessage());
          }
          switch (orientation) {
        	  case VERTICAL:
        	  default:
        	    barWidth = maxLabelWidth;
        	    resize(Math.max(columns * (barWidth + barSpacing),
        			    titleFontMetrics.stringWidth(title)) +
        		   titleFont.getSize() + 5,
        		   (max * scale) + (2 * titleFont.getSize()) + 5 + titleFont.getSize());
        	    break;
        	  case HORIZONTAL:
        	    barWidth = titleFont.getSize();
        	    resize(Math.max((max * scale) + titleFontMetrics.stringWidth("" + max),
        			    titleFontMetrics.stringWidth(title)) + maxLabelWidth + 5,
        		   (columns * (barWidth + barSpacing)) + titleFont.getSize() + 10);
        	    break;
          }
    }
    // standard construction start, stop, run for applet
    // running as thread
    public void start() {
      if (runner == null) {
         runner = new Thread(this);
         runner.start();
      }
    }
    public void stop() {
      if (runner != null) {
         runner.stop();
         runner = null;
      }
    }
    public void run() {
      while (true) {
         if ( refreshTime == 0 ) {
            // stop thread if no refresh required
            stop();
            return;
         }
         pause(refreshTime * 1000);
         // refresh data and repaint graph
         this.getData();
         repaint();
      }
    }
    private void pause(int time) {
      try { Thread.sleep(time); }
      catch (InterruptedException e) {
        if ( debugInfoEnabled ) Debug.write(e.getMessage());
      }
    }
    public synchronized void paint(Graphics g) {
    	int i, j;
    	int cx, cy;
    	char l[] = new char[1];
    	// draw the title centered at the bottom of the bar graph
    	g.setColor(Color.black);
    	i = titleFontMetrics.stringWidth(title);
    	g.setFont(titleFont);
    	g.drawString(title, Math.max((size().width - i)/2, 0),
    		     size().height - titleFontMetrics.getDescent());
    	for (i=0; i < columns; i++) {
    	    switch (orientation) {
    	      case VERTICAL:
    	      default:
    		// set the next X coordinate to account for the label
    		// being wider than the bar size().width.
    		cx = (Math.max((barWidth + barSpacing),maxLabelWidth) * i) +
    		    barSpacing;
    		// center the bar chart
    		cx += Math.max((size().width - (columns *
    					 (barWidth + (2 * barSpacing))))/2,0);
    		// set the next Y coordinate to account for the size().height
    		// of the bar as well as the title and labels painted
    		// at the bottom of the chart.
    		cy = size().height - (values[i] * scale) - 1 - (2 * titleFont.getSize());
    		// draw the label
    		g.setColor(Color.black);
    		g.drawString((String)labels[i], cx,
    			     size().height - titleFont.getSize() - titleFontMetrics.getDescent());
    		// draw the shadow bar
    		if (colors[i] == Color.black) {
    		    g.setColor(Color.gray);
    		}
    		g.fillRect(cx + 5, cy - 3, barWidth,  (values[i] * scale));
    		// draw the bar with the specified color
    		g.setColor((Color)(colors[i]));
    		switch (styles[i]) {
    		  case SOLID:
    		  default:
    		    g.fillRect(cx, cy, barWidth, (values[i] * scale));
    		    break;
    		  case STRIPED:
    		    {
    			int steps = (values[i] * scale) / 2;
    			int ys;
    			for (j=0; j < steps; j++) {
    			    ys = cy + (2 * j);
    			    g.drawLine(cx, ys, cx + barWidth, ys);
    			}
    		    }
    		    break;
    		}
    		g.drawString("" + values[i],
    			     cx,
    			     cy - titleFontMetrics.getDescent());
    		break;
    	      case HORIZONTAL:
    		// set the Y coordinate
    		cy = ((barWidth + barSpacing) * i) + barSpacing;
    		// set the X coordinate to be the size().width of the widest
    		// label
    		cx = maxLabelWidth + 1;
    		cx += Math.max((size().width - (maxLabelWidth + 1 +
    					 titleFontMetrics.stringWidth("" +
    							       max) +
    					 (max * scale))) / 2, 0);
    		// draw the labels and the shadow
    		g.setColor(Color.black);
    		g.drawString((String)labels[i], cx - maxLabelWidth - 1,
    			     cy + titleFontMetrics.getAscent());
    		if (colors[i] == Color.black) {
    		    g.setColor(Color.gray);
    		}
    		g.fillRect(cx + 3,
    			   cy + 5,
    			   (values[i] * scale),
    			   barWidth);
    		// draw the bar in the current color
    		g.setColor((Color)(colors[i]));
    		switch (styles[i]) {
    		  case SOLID:
    		  default:
    		    g.fillRect(cx,
    			       cy,
    			       (values[i] * scale),
    			       barWidth);
    		    break;
    		  case STRIPED:
    		    {
    			int steps = (values[i] * scale) / 2;
    			int ys;
    			for (j=0; j < steps; j++) {
    			    ys = cx + (2 * j);
    			    g.drawLine(ys, cy, ys, cy + barWidth);
    			}
    		    }
    		    break;
    		}
    		g.drawString("" + values[i],
    			     cx + (values[i] * scale) + 3,
    			     cy + titleFontMetrics.getAscent());
    		break;
    	    }
    	}
    }
}

DCF Class Descriptions

You'll need to know the details of the DCF classes, including their variables, constructors, and methods, if you want to use all the features of the DCF.


Class allaire.dcf.recordset.Query. The public class Query extends java.lang.Object. This class executes Cold Fusion templates on remote servers. The templates typically contain one or more SQL queries, which are returned in text format and then parsed into Recordset objects for use by the client object. The constructor for this class is

public Query(String strTemplateURL)

which creates a new query object that's linked to a template on a remote server. Constructing a Query object does not cause the Query to be executed (this is accomplished by using the execute() method). The strTemplateURL parameter is the fully qualified URL of the template to be used by the Query.

The addParam method,

public void addParam(String strName, String strValue)

adds a named parameter to the query, which will be URL-encoded and appended to the URL submitted to the remote server.

The resetParams method,

public void resetParams()

resets the parameter list to empty. This erases all parameters previously added by using the addParam() method.

The execute method,

public void execute() throws Exception

executes the template on the remote server by using the specified parameters. All Recordsets returned by the template will be parsed by using the getRecordset class methods.

The getRecordset method,

public Recordset getRecordset() throws Exception

or

public Recordset getRecordset(String strName) throws Exception

gets the Recordset, or one of a group of named Recordsets, fetched by the query from the remote template.

The getRecordsets method,

public Dictionary getRecordsets()

returns all Recordsets from the query.


Class allaire.dcf.recordset.Recordset. The public class Recordset extends java.lang.Object. This class represents comma-delimited data from templates as a set of rows and columns. Recordsets can be created from applet parameters or executed queries.

The constructor for this class is

public Recordset()

which creates a new empty Recordset. Usually, you will have already created a recordset by using the Query or AppletParamRecordset classes.

The getRowCount method,

public int getRowCount()

returns the number of rows in the Recordset.

The getColumnNames method,

public Vector getColumnNames()

returns the column names in the recordset.

The columnExists method,

public boolean columnExists(String strColumnName)

checks whether the column named in the parameter exists in the Recordset.

The getData method,

public String getData(int iRow, String strColumnName) throws Exception

returns the data value from the intersection of the row value and the column name.

The getColumnData method,

public Vector getColumnData(String strColumnName) throws Exception

returns all the data from the named column.


Class allaire.dcf.recordset.AppletParamRecordset. The public class AppletParamRecordset extends Recordset. This class is used to instantiate a Recordset from applet parameters.

This class can be constructed two ways:

public AppletParamRecordset(Applet applet) throws Exception

or

public AppletParamRecordset(Applet applet, String strName) throws Exception

The second allows you to specify multiple named Recordsets within your template, and refer to them by using separate AppletParamRecordset instances.

The methods for this class are all inherited from the Recordset class; there are none specific to this class.

Using the DCF to Update Data

The DCF is designed primarily to allow you to display data from within a Java applet. It doesn't fare so well when updating. You can write an applet to update data by calling a template with an update CFQUERY in it rather than a select query, but you will be limited in what you can update. This is because the DCF methods don't support the HTTP Post method, only GET. So, you can send as much data to an update query as you can fit on a URL by using the Query.addParam method listed above.

From Here...

By using the DCF, Java applets on your Web pages can actually provide functionality. You've seen how to use the Graphlets provided with Cold Fusion, and you've explored the capabilities of the DCF.

The next chapters cover facets of Cold Fusion not directly related to writing Cold Fusion template code:


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.