Client-Side DHTML

Client-side use of the wfc.html package provides the Java programmer with significant capabilities, such as the ability to manipulate HTML objects. While the code for client-side apps is similar to that of a WFC applet, the results look and feel like conventional HTML rather than like applet output.

This power comes at a price. The html package can be accessed on the client side only by Microsoft Internet Explorer version 4 or later. This means that HTML pages that utilize the html package on the client side can't run on the Internet unless you can limit access to Internet Explorer 4 users.

Thus, a company might apply client-side DHTML for intranet pages used by its sales force, because the company can control which Web browsers are used. In such a situation, client-side DHTML can also provide a significant increase in performance for intranet sites. For Internet sites, however, accessing the html package from the client side isn't practical.

Client-side DHTML is most easily explained by example. The following SimpleClientSide example app demonstrates the principles of the html package. Following that is a more involved example demonstrating access to a client database.

SimpleClientSide app

The SimpleClientSide example app modifies two fields on an existing HTML page while adding two more. The app also demonstrates the user interaction capability for both the existing objects and the added objects.

HTML editor work

From within the Visual J Plus Plus v6 environment, choose New Project from the File menu. In the New Project window, select Web Pages from the Visual J Plus Plus Projects folder. Now choose Code-behind HTML. Provide the name SimpleClientSide as the project name within a DHTML directory, and choose Open.

From within Project Explorer, open the Page1.htm file. The HTML editor opens in Design mode. In this mode, you can edit the HTML page visually. This initial HTML page display contains a warning that only the Microsoft Virtual Machine for Java beyond a certain version can display client-side DHTML, followed by the string "This is bound text."

Switch the HTML editor into Source mode. In this mode, the editor displays the underlying HTML code. The <BODY> section of our initial HTML file is broken into three parts. The first part contains the version warning, bracketed between <HR> tags. Delete this part. Retain the second part that begins with the <OBJECT> tag, and retain the final part that begins with the HTML comment <!-- Insert HTML here -->.

Return the HTML editor to Design mode, to update the HTML file with your edits. (This time, only the sentence "This is bound text." should be displayed.) Select the HTML section of the Toolbox. You'll see that all of the HTML tools are enabled. Drag a Line Break control to the editor, and drop it immediately underneath the existing string. (Direct positioning of controls isn't supported by DHTML. The HTML editor does support a special mode called Absolute mode, in which objects can be placed anywhere within the display window by means of a special COM object. In Absolute mode, however, client-side DHTML isn't supported.)

Now drag a Button control to the HTML editor below the Line Break control. With the Button still selected, open the Properties window. Change the value property of the Button control to Change Bound Text.

Finally, drag a Paragraph Break control to the right of the Button or underneath the Button, to provide extra space following the button and any subsequent objects.

Now switch the HTML editor back to Source mode, and if the space command (&nbsp) is present, delete it. The resulting source code should appear as follows (I have edited this section slightly so that it fits on the tutorial page):

<!-- Insert HTML here -->
<SPAN id="bindText">This is bound text.</SPAN>
<BR>
<INPUT id=button1 name="button1" type=button value="Change Bound Text">
<P></P>


Display the result in the Quick View mode of the HTML editor. The Quick View mode of the editor provides a rapid way of viewing what the DHTML file will look like when viewed by the Internet Explorer browser. Figure 16-1 shows the result.

Screenshot

Screenshot-1. The initial appearance of the edited Page1.htm file.

Close the HTML editor.

wizard-generated code

Open the Class1.java file in the Text editor. The code generated by the Code-behind HTML Wizard appears as follows (I have edited the comments to fit within the tutorial page):

import com.ms.wfc.html.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
public class Class1 extends DhDocument {
 DhText createdText;
 DhText boundText;
 public Class1() {
 // Required for Visual J Plus Plus Form Designer support
 initForm(); // TODO: Add any constructor code after initForm call
 }
 /**
 * Class1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose() {
 super.dispose();
 }
 /**
 * Add all your initialization code here. The code here can add * elements, set bindings to existing elements, set new properties * and event handlers on elements. The underlying HTML document is * not available at this stage, so any calls that rely on the * document being present should be added to the onDocumentLoad()
 * function below (client-only).
 */
 private void initForm() {
 createdText = new DhText();
 createdText.setText("Created Text");
 boundText = new DhText();
 boundText.setID("bindText");
 boundText.setBackColor(Color.LIGHTGRAY);
 /**
 * setBoundElements() will take a list of elements that already * are present in the HTML document, and binds them to the * DhElement list.
 */
 setBoundElements(new DhElement[] {boundText});
 /**
 * setNewElements() takes a list of DhElements and adds them
 * to the HTML document.
 */
 setNewElements(new DhElement[] {createdText});
 }
 /**
 * Client-side only.
 * * Here, the underlying HTML document is available for inspection. * Add any code that wants to get properties of elements or inspect
 * the document in any other way.
 */
 protected void onDocumentLoad(Object sender, Event e) {
 }
}


Let's examine this code for a second. You can see that the class Class1 extends the class DhDocument. The names of all classes in the html package begin with the letters Dh. For the most part, the remainder of an html class name is the same as the WFC equivalent. For example, the class name for an html button is DhButton, whereas the WFC class name would be Button.

The class DhDocument describes the HTML document. (The class DhWindow describes the browser window in which the document resides. Parallel concepts are found in JScript.)

The Class1 constructor immediately invokes the initForm() method, in much the same fashion as the constructor for a WFC-generated class.

The initForm() method contains example code to perform two different tasks: adding objects to the DHTML file and binding the code to objects already present. Unfortunately, the code for these two functions is intertwined in the system-generated listing. Let me display the two tasks separately to better explain each.

Adding DHTML objects is implemented by the following code:

 private void initForm() {
 createdText = new DhText();
 createdText.setText("Created Text");
 /**
 * setNewElements() takes a list of DhElements and adds them
 * to the HTML document.
 */
 setNewElements(new DhElement[] {createdText});
 }


Here, you can see that initForm() creates an object of class DhText. This is the DHTML equivalent of the WFC Text object. The text property of the DhText object is then set to Created Text.

The call to setNewElements adds an array of DhElement objects to the HTML display. In this case, the array contains a single element, the createdText object. The class DhText extends the class DhElement, as do all other Dh component classes.

The second task is represented by the following portion of initForm():

 private void initForm() {
 boundText = new DhText();
 boundText.setID("bindText");
 boundText.setBackColor(Color.LIGHTGRAY);
 /**
 * setBoundElements() will take a list of elements that already * are present in the HTML document, and binds them to the * DhElement list.
 */
 setBoundElements(new DhElement[] {boundText});
 }


This portion begins by creating a DhText object just as we did before. Rather than set the properties of this object directly, however, this code uses the setID() method to attach to the text object bindText, which already appears in the Page1.htm file. Look back to the Page1.htm file to see that the initial text string is contained within <SPAN> tags carrying the id value bindText.

A string of text doesn't allow for the definition of properties such as the id property. To address this limitation, DHTML allows for the inclusion of the <SPAN> tags, which can encompass a section of code and which do have an id property. Defining a property within the <SPAN> tags applies that property to the entire span of DHTML text.

The setID() method scans the DHTML document looking for a text span with the ID bindText. Once found, this text is then bound to the boundText object. The boundText.setBackColor() method changes the background color property of the text span to LIGHTGRAY. (This is a rather subtle change. For a more dramatic effect, try setting a different property, such as font, and redisplaying the result in the Quick View window. You'll see this type of setting demonstrated later in this section.)

user-defined code

Edit the Class1.java file so it looks as follows:

import com.ms.wfc.html.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
/**
 * Demonstrate html package principles by adding a few
 * simple HTML tags and then updating them dynamically.
 */
public class Class1 extends DhDocument {
 DhText createdText;
 DhText boundText;
 public Class1() {
 // Required for Visual J Plus Plus Form Designer support
 initForm(); }
 /**
 * Class1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose() {
 super.dispose();
 }
 /**
 * Bind to a few existing elements and add a few more.
 */
 private void initForm()
 {
 // attach to the text span and update the font
 boundText = new DhText();
 boundText.setID("bindText");
 Font font = new Font(boundText.getFont(), 18, FontSize.PIXELS);
 boundText.setFont(font);
 // attach to the boundButton id
 DhButton boundButton = new DhButton();
 boundButton.setID("button1");
 boundButton.addOnClick(
 new EventHandler(this.onBoundButtonClick)
 );
 // --- created stuff ---
 // add some initial tags
 DhRawHTML start = new DhRawHTML("<P>");
 // add a new text string
 createdText = new DhText();
 createdText.setText("Created Text");
 // now add a break
 DhRawHTML raw = new DhRawHTML("<BR>");
 // finally attach a button
 DhButton button2 = new DhButton("Say Hello");
 button2.addOnClick(new EventHandler(this.onButtonClick));
 // now attach some ending text
 DhRawHTML end = new DhRawHTML("</P>");
 /**
 * setBoundElements() will take a list of elements that already
 * are present in the HTML document, and binds them to the
 * DhElement list.
 */
 setBoundElements(new DhElement[] {boundText, boundButton});
 /**
 * setNewElements() takes a list of DhElements and adds them
 * to the HTML document.
 */
 setNewElements(new DhElement[] {start, createdText, raw, button2,
 end});
 }
 public void onButtonClick(Object o, Event e)
 {
 createdText.setText("Clicked");
 }
 public void onBoundButtonClick(Object o, Event e)
 {
 boundText.setText("Bound clicked");
 }
 /**
 * Client-side only.
 */
 protected void onDocumentLoad(Object sender, Event e) {
 }
}


This version of initForm() starts out as before, by binding Class1 to the existing bindText object we defined in our HTML file. Rather than set the background color, this version changes the font to an 18-point font, which is much larger than the original font.

The next section of initForm() binds an object boundButton to the button1 object we added using the HTML editor. This section adds an event handler onBoundButtonClick() to the boundButton object. Notice that the mechanism for adding an event handler to a DHTML object is identical to the mechanism used to add an event handler to a WFC object in a Windows app. A newly created EventHandler delegate is passed to the addOnClick() method. The onBoundButtonClick() method must follow the format required by the EventHandler delegate.

Looking ahead, the onBoundButtonClick() method changes the text of the boundText object to "Bound clicked".

The "created stuff" section goes through the steps of creating a DhText object and a DhButton object, and then attaching an onButtonClick() method to the button. The calls to DhRawHTML() allow you to create DhElement objects containing raw DHTML commands. These objects can be inserted into the DHTML stream along with more sophisticated html package objects.

The setBoundElements() method adds the bound objects to the DhDocument container. The setNewElements() method adds the newly created elements to the DHTML file and to the DhDocument container.

result

Build the resulting SimpleClientSide project. Notice that Visual J Plus Plus v6 creates and signs the CAB file referenced in the Page1.htm file. Display Page1.htm in the HTML editor in Quick View mode. Before the HTML file is displayed, a dialog box appears to make sure that the user agrees to allow the SimpleClientSide app full access. A page containing client-side DHTML must be accepted as trusted by the user, because the browser doesn't know the intent of the SimpleClientSide.cab object. Choose Yes to proceed.

Screenshot-2 shows the output of the Quick View window as the Page1.htm page appears initially. Figure 16-3 shows the Quick View window after both buttons have been chosen.

Screenshot

Screenshot-2. The initial appearance of the SimpleClientSide app window.

Screenshot

Screenshot-3. The appearance of the SimpleClientSide app window after both buttons have been clicked.

The change from Figure 16-2 to Figure 16-3 demonstrates the principles of DHTML better than anything we've seen so far. Choosing either button results in an immediate change in the corresponding HTML text without the aid of the server and without the use of an applet. Try executing Page1.htm from within Internet Explorer, and then view the HTML source code from the browser. You'll see that there is no trace in the source HTML code of the second section of text or the second button, nor is there a trace of the text as modified after you choose either of the buttons—and yet these changes are displayed in the browser. Both of these facts are a result of the ability of the CAB file app to modify the browser display dynamically.

A comment

While the SimpleClientSide app demonstrates that it's possible to create new HTML objects (createdText = new DhText()), binding to existing objects (boundText.setID("bindText")) seems more fruitful. It's much easier to create an HTML file using your favorite HTML editor than it is to create the code to display HTML pages dynamically. (There is nothing magical about the HTML editor in Design mode. You can use whatever editor you prefer, as long as it assigns each object an ID. Even if the editor doesn't assign each object an ID, you can add it by hand.)

Once the HTML objects have been created, it's relatively simple to bind to them and then add the necessary addOnX() methods to support the functionality you want.

(Notice that I have been avoiding using the term "applet" when describing the SimpleClientSide app. SimpleClientSide isn't an applet, even though it's referenced from an HTML page.)

Accessing a Database Using Client-side DHTML

While it is certainly interesting to see the Dynamic HTML changes in SimpleClientSide, the app doesn't do anything particularly interesting. The following DataClientSide app represents a practical use of the html package. This app reads a local database at run time, and displays the results in a DHTML table.

HTML editor work

Use the Code-behind HTML wizard to create the DataClientSide app in your DHTML directory. In Source mode, remove the warning section from the Page1.htm file. Also remove the <SPAN> tag that contains the "This is bound text." string. Leave the <OBJECT> tags.

database access code

While it would be possible to write the entire DataClientSide app in a single class, the app will be easier for you to grasp if it's divided into at least two classes. Add the following TableDatabaseConnection class to your project to provide simple access to a Microsoft Access 97 database. This class is very similar both in concept and structure to those created in Chapter 12, Accessing Databases. (If you don't understand this class, return to for a more detailed discussion.)

import com.ms.wfc.html.*;
import com.ms.wfc.core.*;
import com.ms.wfc.data.*;
import com.ms.wfc.data.ui.*;
/**
 * Extends DataSource to form a convenient database connection to
 * a DhTable object.
 */
public class TableDatabaseConnection extends DataSource
{
 /**
 * Build datasource
 * @param pathName the path to a Microsoft Access 97 database
 * @param databaseName the name of the file (including extension)
 * @param tableName table to extract from the database
 * @param fields fields to read from that table
 */
 public TableDatabaseConnection(String pathName,
 String databaseName,
 String tableName,
 String[] fields)
 {
 // construct the connection string to the specified file
 String s = "PROVIDER=MSDASQL;dsn=MS Access 97 Database;" +
 "uid=;pwd=;DBQ="
 + pathName
 + "\\"
 + databaseName;
 setConnectionString(s);
 // set up the query
 setCommandText(buildQuery(tableName, fields));
 // initialize miscellaneous settings
 setCursorLocation(
 com.ms.wfc.data.AdoEnums.CursorLocation.CLIENT);
 setLockType(com.ms.wfc.data.AdoEnums.LockType.OPTIMISTIC);
 setSort(null);
 setStayInSync(true);
 setUserId(null);
 setPassword(null);
 }
 /**
 * Build a SQL query from the table name and the field names.
 */
 static private String buildQuery(String tableName, String[] fields)
 {
 // start with select statement
 String s = "select ";
 // now tack on the field names with a comma in between
 // each name (but no comma after the last field)
 int i = 0;
 while(true)
 {
 s += fields[i++];
 if (i >= fields.length)
 {
 break;
 }
 s += ",";
 }
 // add the table name
 s += " from " + tableName;
 // return the SQL select statement
 return s;
 }
 /**
 * Build a DataBinding array that binds the fields in the
 * database table to the columns in the repeater row of the table
 * @param fields field names from the database table to bind to
 * @param columns columns in the repeater row to bind to
 */
 public static DataBinding[] buildDataBindings(String[] fields, DhCell[] columns)
 {
 // create a set of data bindings consisting of
 // a data binding for each field
 DataBinding[] dataBindings = new DataBinding[fields.length];
 for(int i = 0; i < dataBindings.length; i++)
 {
 dataBindings[i] = new DataBinding(columns[i],
 "Text",
 fields[i],
 null);
 }
 // return the data bindings
 return dataBindings;
 }
}


The TableDatabaseConnection constructor begins by creating an Access 97 access string from the pathname and databaseName values. (The databaseName value is the name of the database file). This connection string is passed to DataSource.setConnectionString() to become the command string used by the base DataSource class to access the database.

The constructor continues by using the buildQuery() method to create a SQL query from the table name and the field names passed to the constructor. The resulting query is passed to the DataSource.setCommandText() method to be used as the database query. The remaining calls in the constructor set DataSource properties.

The buildQuery() method takes as arguments the tableName string and the fields array of strings containing the field names, and constructs a query from them. Suppose, for example, that the tableName value was "A" and that the fields array contained the strings "B", "C", and "D". From this, buildQuery() would create the following SQL query:

SELECT B,C,D FROM A


The final method in the class, buildDataBindings, creates and returns an array of DataBinding objects. The length of this array is the same as the number of strings in the fields array. Each DataBinding object binds a field name to a DhCell class. (We don't yet know what a DhCell class is, but for now suffice it to say that it's the equivalent of a text field in the examples in . If you aren't familiar with the concept of a data binding, you can return to for a more complete discussion.)

Class1 code

Armed with the TableDatabaseConnection class, the DataClientSide Class1 class is relatively simple. The only new concept presented by this code is the way that HTML tables are constructed using the DhTable, DhRow, and DhCell classes:

import com.ms.wfc.html.*;
import com.ms.wfc.core.*;
import com.ms.wfc.data.*;
import com.ms.wfc.data.ui.*;
/**
 * Demonstrate accessing a local database via the WFC html package.
 */
public class Class1 extends DhDocument {
 // --------------define the database information------------------
 // path to the database
 String pathName = "C:\\ProgramVJ\\" + "Windows Database apps\\Databases";
 // the database name
 String databaseName = "MyCompany.mdb"; // define the table name
 String tableName = "Customers";
 // define the fields to extract from the database table
 String[] fields = new String[]
 {"ContactFirstName",
 "ContactLastName",
 "PhoneNumber"};
 // ---------------end of database info----------------------------
 // class constructor
 public Class1() {
 // Required for Visual J Plus Plus Form Designer support
 initForm(); }
 /**
 * Class1 overrides dispose so it can clean up the
 * component list.
 */
 public void dispose() {
 super.dispose();
 }
 /**
 * Create a table from the contents of a client-side database.
 */
 private void initForm() {
 // create data source designed for connection to
 // a DhTable
 DataSource dataSource = new TableDatabaseConnection(
 pathName,
 databaseName,
 tableName,
 fields);
 // --- create a table using that data ---
 // start the table
 DhTable dhtable = new DhTable();
 dhtable.setPageSize(10);
 dhtable.setBorder(3);
 // generate the header row
 DhRow headerRow = new DhRow();
 DhCell headerCol = new DhCell("Header");
 headerCol.setColSpan(3);
 headerCol.setAlign(DhAlignment.CENTER);
 headerRow.add(headerCol);
 dhtable.setHeaderRow(headerRow);
 // now create a template row
 DhRow repeaterRow = new DhRow();
 DhCell[] columns = new DhCell[fields.length];
 for(int i = 0; i < columns.length; i++)
 {
 columns[i] = new DhCell();
 repeaterRow.add(columns[i]);
 }
 // attach the template row to the table
 dhtable.setRepeaterRow(repeaterRow);
 // attach the data source to the table
 dhtable.setDataSource(dataSource);
 // attach the database to the columns of the table
 DataBinding[] dataBindings = TableDatabaseConnection.buildDataBindings(fields, columns);
 dhtable.setDataBindings(dataBindings);
 // start the datasource
 dataSource.begin();
 /**
 * setNewElements() takes a list of DhElements and adds them
 * to the HTML document.
 */
 setNewElements(new DhElement[] {dhtable});
 }
 /**
 * Client-side only.
 * * Here, the underlying HTML document is available for inspection. * Add any code that wants to get properties of elements or inspect
 * the document in any other way.
 */
 protected void onDocumentLoad(Object sender, Event e) {
 }
}


The data members of Class1 define the path to the database and the table to access within that database. (This app accesses the same Customers table of the MyCompany database that we used in . Because we're using the same database in this chapter as we used in , you can compare the output of this app with the output of the ToolboxADO example in .) The values in the fields array specify that the app is to access the first name, last name, and phone number of each customer in the Customers table.

The only method we modified from its default version is the initForm() method. The initForm() method begins by establishing a DataSource object using the TableDatabaseConnection class described previously. The initForm() method continues by creating a DhTable object that will access the entries present in the Customers database table.

To understand the way DhTable works, you must first understand how an HTML table is constructed. An HTML table is defined with the <TABLE> tag. The first row of the table—the header row—is contained within the span of a <THEAD> tag. The table header is optional. Below the header appears a sequence of rows. Each row is defined by a <TR> tag. Each column within the row is defined by a <TD> tag. The <TD> elements are called cells rather than columns, since they really represent a data entry within the row rather than an entire column. Each row can have a different number of cells than its neighbors.

The code for a DHTML table is analogous to the HTML code. The table is defined by the DhTable class. In our Class1 example the first few method calls that follow the creation of the DhTable object set the maximum page size to 10 rows, and set the border width to three pixels.

The next block of code defines the header row to consist of a single cell that spans three columns and contains the centered text "Header." As you'll see shortly, since the entire table has only three columns, this effectively places a banner across the top of the entire table.

Normally, the next sequence would be to add a number of DhRow objects to the table, each with a set of DhCell objects attached. However, building a table with an unknown number of entries adds a certain wrinkle. To handle this case, DhTable allows you to define a "repeater" row, sometimes called a template row. This row is repeated for each entry in the database up to the page size defined earlier. The initForm() method constructs this template row by creating a cell for each entry in the fields array. Thus, the ContactFirstName, ContactLastName, and PhoneNumber database fields are each assigned a column in the resulting HTML table. The template row is added to the table using the setRepeaterRow() method. (A normal row would be added to a table using the conventional add() method.)

Once the repeater row has been defined, the DataSource created previously is attached to the HTML table. An array of DataBinding objects, one object for each field in the database, is created using the buildDataBindings() method defined earlier in the TableDatabaseConnection class. This dataBindings array is also attached to the table.

Finally the flow of data is started by calling dataSource.begin(), and the HTML table is attached to the DHTML page using the setNewElements() method.

(You'll notice how the DhTable class uses the DataSource and the DataBinding classes in the same way as the DataBinder class did in .)

result

The result of executing the DataClientSide app in Quick View mode of the HTML editor is shown in Figure 16-4. While the output of this example app lacks the window dressings a real-world DHTML app would have, this example clearly demonstrates the results you can obtain by using the html package with a client-side database: the DataClientSide app modified an existing DHTML file in real-time in order to display the contents of a client-side database.

Screenshot

Screenshot-4. The DataClientSide app displays the contents of the Customers table with a DhTable object. Comments