Share via


Programming Dynamic HTML in Java

 

Microsoft Corporation

July 13, 1998

Introduction

With Microsoft® Internet Explorer 4.0, Microsoft introduced its implementation of a revolutionary HTML object model that content providers can use to effectively manipulate HTML properties on the fly. Until now, this object model has primarily been accessed using script technology. The com.ms.wfc.html package of the Windows Foundation Classes for Java (WFC) framework now lets you access the power of Dynamic HTML (DHTML) on a Web page directly from a Java class.

The following topics are covered in this section:

  • Introduction to the com.ms.wfc.html Package
  • Using the initForm Method
  • Understanding the DhElement Class
  • Working with Containers
  • Handling Events
  • Using Dynamic Styles
  • Working with Dynamic Tables
  • Using the com.ms.wfc.html Package on the Server

Quick Start

To help you get up and running using the com.ms.wfc.html package to implement Java and DHTML, here are the basic steps you can perform to create a simple DHTML project and add your own dynamic behavior to it. While this is by no means the entire story, it sets the stage for the rest of this topic and for the samples. There are five basic steps when using the com.ms.wfc.html package:

  1. Create a new project by choosing New Project from the File menu and selecting Code-behind HTML from the Web Pages category.

    This generates a project containing a class called Class1, which extends DhDocument. This class represents the dynamic HTML document. You add initialization code to its initForm method to control the document's contents and behavior.

  2. You can now extend the behavior of your document by doing the following:

    • Create new elements (such as DhButton) or create element objects that represent existing elements in the document (on the HTML page).
    • Hook event handlers into some of your elements.
    • In your Class1.initForm method, add the new elements using the setNewElements method, and bind any existing elements using the setBoundElements method.
  3. Write the event handler methods you hooked up in step 2.

Your document class will look something like this:

import com.ms.wfc.html.*;
import com.ms.wfc.core.*;
import com.ms.wfc.ui.*;
public class Class1 extends DhDocument
{
   public Class1()
   {
       initForm();
   }

   // Step 2: create objects to represent a new elements…
   DhButton newElem = new DhButton();
   // … as well as elements that already exist in the HTML page.
   DhText existElem = new DhText();
        
   private void initForm( )
   {
      // Set properties to existing elements and newly added elements.
      newElem.setText("hello world");
      existElem.setBackColor(Color.BLUE);
      // Step 3: hook up an event handler to your object.
      newElem.addOnClick(new EventHandler(this.onClickButton));
      // Step 2: create an object to represent an existing element.
      existElem = new DhText();
      // Step 4: call setNewElements with an array of new elements.
      setNewElements(new Component[] { newElem });
      // Step 5: call bindNewElements with an array of existing elements.
      setBoundElements(new DhElement[]{ existElem.setBindID("Sample") });
   }
        
   // Step 6: implement your event handler
   private void onClickButton(Object sender, Event e) {
           existElem.setText("Hello, world");
   }
}

The Java portion of the exercise is complete. The other part is the HTML code. The following example shows a simplified version of the HTML document generated by the Code-behind HTML project template. There are two HTML elements that connect this HTML to the code in your project:

  • The <OBJECT> tag loads the com.ms.wfc.html.DhModule class, which is instantiated by the Virtual Machine for Java.
  • The <OBJECT> tag has a parameter called CODECLASS. The value of this parameter is the name of the user class that extends DhDocument (for example, Class1).
<HTML>
<BODY>
<OBJECT classid="java:com.ms.wfc.html.DhModule" 
        height=0 width=0 ... VIEWASTEXT>
<PARAM NAME=CABBASE VALUE=MyProject>
<PARAM NAME=CODECLASS VALUE=Class1>
</OBJECT>
<span id=Sample></span>
<!-- Insert your own HTML here -->
</BODY>
</HTML>

Open Internet Explorer 4.0, point it at your HTML file, and you can see your application run.

Using the initForm Method

The initForm method plays a central role in the programming model for all user interface programming in WFC. When using the Visual J++™ Forms Designer for Win32-based applications, initForm is found in the Form-derived class that represents your main form. In the com.ms.wfc.html package, this method is found in your DhDocument-derived class (for example, Class1 in the code-behind HTML template provided by Visual J++) and is called from the constructor of the class.

You should use the initForm method to initialize the Java components that represent the HTML elements you want to access and code to. As with the initForm method in Form-derived classes, there are certain restrictions when calling WFC methods from initForm in DhDocument. As a rule, you should call only methods in initForm that set properties. Moreover, you should bind only to elements on the HTML page using the setBoundElements method.

Specifically, this means that calling any method that resets or removes a property or element is strictly not supported in initForm. This also applies to any methods that attempt to locate elements on the existing HTML page (such as DhDocument.findElement).

The reason for this is that the document on the existing HTML page is not merged with your DhDocument-derived class until the DhDocument.onDocumentLoad method is called. You can use the onDocumentLoad method to retrieve properties and manipulate or locate elements in the existing HTML document. For information on using the initForm and onDocumentLoad methods on server-side classes, see the section below, "Using the com.ms.wfc.html Package on a Server."

Understanding the DhElement Class

Elements are objects derived from DhElement, which is the superclass of all user interface elements in the com.ms.wfc.html package. There is a certain consistency you can count on when using any object derived from DhElement:

  • Every element has an empty constructor. Therefore, you can instantiate any element with a new statement and then set properties, hook event handlers, and call methods consistently.
  • Elements are modeless. Setting properties or calling methods always works in any order and is not conditional on some external state or circumstance.
  • Every container has an add method that takes the type-safe element that is appropriate for it.
  • In the browser environment, an element does not become visible to the end user until you add it (or the topmost container element in its parentage) to the document. However, this is merely an artifact and not part of the programming model. You don't have to change the way you program to elements because they work the same way whether they are visible or not.

If an element is already on the page when the DhDocument.onDocumentLoad method is called, you can call the document's findElement method and start programming to that element. You can also call setBoundElements from initForm to merge known elements on the page with elements in your DhDocument-derived class. (The findElement method has better performance but specifically requires that onDocumentLoad is called first.)

The searching routine used by findElement and setBoundElements assumes that the element you want to bind to has an ID attribute set to a particular name. Using findElement, you can also enumerate all the elements in the document until you find the one you are interested in.

Working with Containers

Containers are elements that can hold other elements. A basic example is the <DIV> element, which can contain any other HTML item. More complex examples include table cells and, of course, the document itself. In most cases, containers can be arbitrarily nested, such as having a table inside a cell of another table.

Containers are like other elements. They are created with a new statement, and many can be positioned and sized on the page. You can position and size elements within a container and set up their z-order relationships. One of the powerful features of DHTML is that you can then change any of these attributes in your code.

Of course, you can also allow elements within a container to be positioned using the normal HTML layout rules. Call either the setLocation or setBounds method of an element to set its absolute position, or call resetLocation to let the HTML layout engine position it (immediately after the last element in the HTML flow layout).

Once you have created a container element, you can add elements to it using either the setNewElements or add method. This mechanism follows the regular pattern of parent-child relationships: the elements, which can also be other containers, added to the container become its children. None of the elements is actually attached to the document until the topmost container, which is not a part of any other container, is added to the document.

You can position and size a container using its setBounds method. For example, to create a container, type:

         DhForm myForm = new DhForm();
    

You can then set various attributes on the container, including the ToolTip that is shown when the mouse hovers over the panel:

   myForm.setToolTip("This text appears when the mouse hovers");
   myForm.setFont("Arial", 10);
   myForm.setBackColor(Color.RED);
   myForm.setBounds(5, 5, 100, 100);
   

Finally, you can add the container you've just created to the document in your DhDocument-derived class (such as Class1.java):

    this.add(myForm);
   

When adding elements to the container, you can specify where they go in the z-order using one of a set of constants provided by the com.ms.wfc.html package. Elements are added with a default size and position. You can call setBounds on the elements to specify a different size.

   DhForm myOverLay1 = new DhForm();
   DhForm myOverLay2 = new DhForm();
   myOverLay1.setBackColor(Color.BLACK); 
   myOverLay1.setBounds(10, 10, 50, 50);
   myOverLay2.setBackColor(Color.BLUE); 
   myOverLay2.setBounds(20,25, 50, 50);
   myForm.add(myOverLay1, null, DhInsertOptions.BEGINNING);
   // Black on top of blue.
   myForm.add(myOverLay2, myOverLay1, DhInsertOptions.BEFORE);
   // Blue on top of black (uncomment below and comment above ).
   // myForm.add(myOverLay2, myOverLay1, DhInsertOptions.AFTER);
   

You can also use the setZIndex method after the elements are added to move the elements around in the z-order. For example, the following syntax does not explicitly set a z-order on the added element but uses the default z-order (that is, on top of all other elements):

   myForm.add(myText);
   

You can set this explicitly as follows, where num is an integer representing the relative z-order of the element within its container:

   myText.setZIndex(num);
   

The element with the lowest number is at the bottom of the z-order (that is, everything else covers it). The element with the highest number is at the top (that is, it covers everything else).

Handling Events

Many elements in a DHTML program can trigger events. The com.ms.wfc.html package uses the same event model as the com.ms.wfc.ui package. If you are familiar with that mechanism, you'll find little difference between the two. A button is a good example. Suppose you want to handle the event that occurs when a user clicks a button on a page. Here's how:

public class Class1 extends DhDocument
{
   Class1() { initForm();}
   DhButton myButton = new DhButton();
   private void initForm()
   {
      add(myButton);
      myButton.addOnClick(new EventHandler(this.myButtonClick));
   }
   void myButtonClick(Object sender, Event e)
   {
      ((DhButton) sender).setText("I've been clicked");
   }
}

In this code, whenever the button triggers the onClick event (that is, when it is clicked), the myButtonClick event handler is called. The code inside the myButtonClick event handler does very little in this example. It just sets the caption on the button to new text.

Most events propagate all the way up a containment tree; this means that the click event is seen by the button's container and by the button itself. Although typically programmers handle events in the container closest to the event, this event bubbling model can be useful in special cases. It provides the programmer with the flexibility to decide the best place to code the event handlers.

Many different events can be triggered by elements in DHTML, and you can catch them all in the same way. For example, to determine when the mouse is over a button, try the following code, which catches mouseEnter and mouseLeave events for the button:

public class Class1 extends DhDocument
{
   DhButton button = new DhButton();
   private void initForm()
   {
      button.addOnMouseEnter(new MouseEventHandler(this.buttonEnter));
      button.addOnMouseLeave(new MouseEventHandler(this.buttonExit);
      setNewElements( new DhElement[] { button } );
   }
   void buttonEnter(Object sender, MouseEvent e)
   {
      button.setText("I can feel that mouse");
   }
   void buttonExit(Object sender, MouseEvent e)
   {
      button.setText("button");
   }
}

All events that can be triggered (and caught) are defined in the event classes, based on com.ms.wfc.core.Event.

Using Dynamic Styles

You can think of a Style object as a freestanding collection of properties. The term style is borrowed from the word processing world where the editing of a style sheet is independent of the documents to which you apply it. The same is true for using and applying Style objects in this library.

As an example, your boss tells you that the new corporate color is red and you need to change the color of elements in your HTML pages. You can, of course, set properties directly on elements, which is the traditional model for GUI framework programming:

       // old way of doing things...
       DhText t1 = new DhText();
       DhText t2 = new DhText();
       t1.setColor( Color.RED );
       t1.setFont( "arial");
       t2.setColor( Color.RED );
       t2.setFont( "arial");
    

You could, of course, use derivation to save yourself time. For example, you might consider improving this with the following code:

       // old way of doing things a little better...
       public class MyText extends DhText
       {
          public MyText()
          {
              setColor( Color.RED );
              setFont( "arial" );
          }

This works fine until you decide you also want those settings for buttons, labels, tabs, documents, and so on. And you'll find yourself with even more work when you apply these to another part of your program or to another program.

The answer to this problem is a Style object. While using this library, you can instantiate a Style object and set its properties at any point:

       // STEP 1: Create style objects.
       DhStyle myStyle = new DhStyle();
       // STEP 2: Set properties on style objects.
       myStyle.setColor( Color.RED );
       myStyle.setFont( "arial" );

Then at any other time in the code, you can apply that style to any number of elements:

       DhText t1 = new DhText();
       DhText t2 = new DhText();
       // STEP 3: Apply styles using the setStyle method.
       t1.setStyle( myStyle );
       t2.setStyle( myStyle );

When it's time to keep up with the dynamic nature of high-level policy setting at your corporation, the following line sets all instances of all elements with myStyle set on them to change color:

       myStyle.setColor( Color.BLUE );

Here is the really powerful part: All this is available during run time. Every time you make a change to the Style object, the DHTML run time dynamically reaches back and updates all elements to which that Style object is applied.

For more information, see the section below, "Understanding Style Inheritance."

Understanding Style Inheritance

The HTML rendering engine can determine the style to use if conflicting styles are set on an element. For example, if an element has the color property set directly on it (DhElement.setColor), the color defined by the color property is used. However, if an element has a Style object on it (DhElement.setStyle) and that object has the color property set, that value is used. Failing to find a color or a style, the same process is used with the element's container (DhElement.getParent), and failing that, with the container of that container and so on.

The process continues up to the document. If the document doesn't have a color property set on it, the environment (either browser settings or some other environment settings) determines the color to use.

This process is called cascading styles because the properties cascade down the containment hierarchy. The underlying mechanism for DhStyle objects is called Cascading Style Sheets (CSS) by the W3C.

Working with Dynamic Tables

Working with tables is actually no different from any other part of the library; the principles and programming model apply to tables as they do to any other type of element. A table, however, is such a powerful and popular element that it is worth discussing.

To use a table, you create a DhTable object, add DhRow objects to that, and then add DhCell objects to the rows. The following are the rules for table usage:

  • You can add only DhRow objects to a DhTable object.
  • You can add only DhCell objects to a DhRow object.
  • You can add any kind of element to a DhCell object.

While this may seem restrictive, you can easily create a simple container that emulates a GridBag with the following code:

        import com.ms.wfc.html.*;
        public class GridBag extends DhTable
        {
            int   cols;
            int   currCol;
            DhRow currRow;
            public GridBag(int cols)
            {
                this.cols = cols;
                this.currCol = cols;
            }
            public void add(DhElement e)
            {
                if( ++this.currCol >= cols )
                {
                    this.currRow = new DhRow();
                    super.add(currRow);
                    this.currCol = 0;
                }
                DhCell c = new DhCell();
                c.add(e);
                this.currRow.add( c );
            }
        }

To use this GridBag class, you just set the number of rows and columns (they must be the same with this implementation) and then assign elements to cells. The following is an example of the code in your DhDocument-derived class that uses this GridBag:

   protected void initForm()
   {
      GridBag myTable = new GridBag(5);
      for (int i = 0; i < 25; ++i){
         myTable.add(new DhText("" + i));
      setNewElements( new DhElement[] { myTable } );
      }
   }

One of the most powerful uses of the library is the combination of tables and Style objects. This combination enables you to create custom report generators that are powerful, professional looking, and easy to code.

Data Binding to Tables

Tables also have data binding capabilities. Using a com.ms.wfc.data.ui.DataSource object, you can bind data to your table, as shown in the following sample code.

import com.ms.wfc.data.*;
import com.ms.wfc.data.ui.*;
.
.
.
void private initForm( ){
   
   DhTable dataTable = new DhTable();
   dataTable.setBorder( 1 );
   dataTable.setAutoHeader( true );
   
   DataSource dataSource = new DataSource();
   dataSource.setConnectionString("DSN=Northwind");
   dataSource.setCommandText("SELECT * FROM Products" );   
   
   // If you would like to use the table on the server,
   // call dataSource.getRecordset() to force the DataSource
   // to synchronously create the recordset; otherwise,
   // call dataSource.begin(), and the table will be populated
   // when the recordset is ready, asynchronously.
   if ( !getServerMode() ){
      dataSource.begin();
      dataTable.setDataSource( dataSource );
   } else
       dataTable.setDataSource( dataSource.getRecordset() );
   setNewElements( new DhElement[] { dataTable } );
}

If you know the format of the data that is going to be returned, you can also specify a template (repeater) row that the table will use to format the data that is returned. The steps to do this are as follows:

  1. Create your DhTable element:

    DhTable dataTable = new DhTable();
    
  2. Create your template row and set it into the table; you can also optionally create a header row. For each item in the template cell that you would like to receive data from the recordset, create a DataBinding for it.

    DhRow repeaterRow = new DhRow();
    RepeaterRow.setBackColor( Color.LIGHTGRAY );
    RepeaterRow.setForeColor( Color.BLACK );
    DataBinding[] bindings = new DataBinding[3];
    DhCell  cell = new DhCell();
    DataBinding[0] = new DataBinding( cell, "text", "ProductID" );
    repeaterRow.add( cell );
    cell = new DhCell();.
    DataBinding[1] = new DataBinding( cell, "text", "ProductName" );
    cell = new DhCell();.
    cell.setForeColor( Color.RED );
    cell.add( new DhText( "$" ) );
    DhText price = new DhText();
    price.setFont( Font.ANSI_FIXED );
    DataBinding[2] = new DataBinding( price, "text", "UnitPrice" );
    cell.add( price );
    repeaterRow.add( cell );
    // Set up the table repeater row and bindings.
    table.setRepeaterRow( repeaterRow );
    table.setDataBindings( bindings );
    // Create and set the header row.
    DhRow headerRow = new DhRow();
    headerRow.add( new DhCell( "ProductID" ) );
    headerRow.add( new DhCell( "Product Name" ) );
    headerRow.add( new DhCell( "Unit Price" ) );
    table.setHeaderRow( headerRow );
    
  3. Create a DataSource object, and set it to retrieve data in the format you expect.

    DataSource ds = new DataSource();
    ds.setConnectionString("DSN=Northwind");
    ds.setCommandText("SELECT ProductID, ProductName, UnitPrice FROM Products WHERE UnitPrice < 10" );
    
  4. Set the DataSource into the DhTable object.

    table.setDataSource( ds );
    ds.begin();
    
  5. Add the DhTable to the document.

    setNewElements( new DhElement[] { table } );
    // alternately: add( table );
    

Your table is now populated with the data from the recordset and formatted like the template row.

Using the com.ms.wfc.html Package on a Server

The com.ms.wfc.html package can also be used on the server to provide a programmatic model for generating HTML and sending it to the client page. Unlike the client-side Dynamic HTML model, the server-side model is static because the server Java class has no interaction with the client document. Instead, the server composes HTML elements and sends them off sequentially to the client as they are encountered in the HTML template if one is specified.

Although not fully dynamic, this is still a powerful server feature. For example, you can apply DhStyle attributes to all parts of some template HTML code and then generate vastly different looking pages by just changing the DhStyle attributes. You do not have to programmatically generate all the individual style changes. Another advantage is that you can use the same model for generating dynamic HTML for both client and server applications, thereby making the HTML generation easier to learn and remember.

There are currently two modes of generating HTML on the server. Both use Active Server Pages (ASP) scripting and a class based on the com.ms.wfc.html classes. The first is the "bare-bones" approach that relies more on the ASP script. The second uses a class derived from DhDocument and is very similar to the model that you use on the client because it places more control inside the class than in the script.

ASP-Based Approach

This approach uses two ASP methods on the server page: getObject and Response.Write. The getObject method is used to instantiate a class based on the WFC com.ms.wfc.html classes; the Response.Write method writes the generated HTML string to the client. The com.ms.wfc.html.DhElement class provides a getHTML method that creates the HTML string; this string is then sent to the client page using the ASP Response.Write method.

For example, you have a class called MyServer that extends DhForm and incorporates some HTML elements. In your ASP script, you first call getObject("java:MyServer") to create a DHTML object. You can then perform whatever actions you want on the object from your ASP script, such as setting properties on the object. When you have finished, you call the object's getHTML method to generate the string and pass that result to the ASP Response.Write method, which sends the HTML to the client. The following code fragments show the relevant ASP script and Java code for creating a DhEdit control in HTML and sending it to the client.

ASP SCRIPT
Dim f,x 
set f = getObject( "java:dhFactory" )
set x= f.createEdit 
x.setText( "I'm an edit!" ) 
Response.Write( x.getHTML() ) 
Response.Write( f.createBreak().getHTML() )
.
.
.
JAVA CODE
public class dhFactory { 
   public dhFactory(){ } 
   
   public DhBreak createBreak() { 
      return new DhBreak(); 
   }
   public DhEdit createEdit(){ 
      return new DhEdit(); 
   }
}

HTML-Based Approach

This approach is slightly more sophisticated and closer to the client model. It still uses an ASP script to site the DhDocument class, but the rest of the operational code is in Java. As in the client model, the DhModule class is instantiated as the Java component on the Web page and automatically calls the initForm method in your project class that derives from DhDocument.

As in the client model, you can do all your binding setup in your initForm call. The onDocumentLoad function is also called for your server-side class. In this method, you can access the IIS Response and Request objects (using the DhModule getResponse and getRequest methods) and also append new DhElement items to your document stream. However, it is important to understand that you cannot use document-level functions, such as findElement, or use enumeration operations on a server-side document, except on items that you have explicitly added to your DhDocument-derived class.

To use the HTML-based approach, follow these steps:

  1. Create your server Java class (extending from DhDocument).
  2. In that class, implement the initForm method as you would with a client application.
  3. From ASP, call the Server.CreateObject method, passing it "DhModule" to create a DhModule object.
  4. Call DhModule.setCodeClass method, passing it the name of your DhDocument-derived class.
  5. Call the DhModule.setHTMLDocument method, passing it the full local file name path of your server Web page as a template if you have one.
  6. If you call setHTMLDocument with an empty string (""), your DhDocument class runs and outputs the HTML for any elements you have added at the location in your ASP where setHTMLDocument is called. You can then generate sections of HTML code inline.
  7. If you do not call setHTMLDocument, the DhDocument class outputs full HTML for the page, including the <HTML>, <HEAD>, and <BODY> tags.

The following sample shows an ASP page that uses a template:

<% Set mod = Server.CreateObject( "com.ms.wfc.html.DhModule" )
   mod.setCodeClass( "Class1" )
   mod.setHTMLDocument( "c:\inetpub\wwwroot\Page1.htm" )
%>

At run time, the framework recognizes that your class is running on a server and acts accordingly.

Once instantiated, you can add elements or text to your DhDocument-derived class. Those items will be appended to any template specified just before the </BODY> tag.

The following sample demonstrates a class that works on either the client or the server.

import com.ms.wfc.ui.*;
import com.ms.wfc.html.*;

public class Class1 extends DhDocument {
   public Class1(){
      initForm();
   }
   DhText txt1 = new DhText();
   DhForm sect = new DhForm();
   private void initForm() {
      
      // Call getServerMode() to check
      // if this object is running on the server.
      if ( getServerMode() ){
          txt1.setText( "Hello from the server!" );
      }else{
          txt1.setText( "Hello from the client!" );
      }
       
      // Size the section, set its background color
      // and add the txt1 element to it.        
      sect.setSize( 100, 100 );        
      sect.setBackColor( Color.RED );
      sect.add( txt1 );
      add( sect );
      setNewElements( new DhElement[] { sect } );
   }
}

If you want to bind to an existing HTML document on the page, use the DhDocument.setBoundElements method, just as you would on the client. For example, if your HTML template contains the following HTML:

<P>
The time is:<SPAN id=txt1></SPAN><BR>
<INPUT type=text id=edit1 value="">
</P>

Your initForm method looks like this:

DhText txt1 = new DhText();
DhEdit edit = new DhEdit();
DhComboBox cb = new DhComboBox();
private void initForm(){
    txt1.setText(com.ms.wfc.app.Time().formatShortTime());
    edit.setText("Hello, world!");
    edit.setBackColor( Color.RED );
    setBoundElements( new DhElement[]{ txt1.setBindID( "txt1" )
                                       edit.setBindID( "edit1" ) } );
    // Create a combo box to be added after the bound items.
    cb.addItem( "One" );
    cb.addItem( "Two" );
    // Add the items to the end of  document.  
    setNewElements( new DhElement[]{ cb });
}

There are very few differences between the interpretation of server and client HTML classes. However, there is one important difference. Once elements are written (sent to the client), they cannot be modified as they can on a client document. The DhCantModifyElement exception, which is relevant only for server applications, is thrown after a write has been performed on an element if an attempt is made to modify that element again. (This underscores the fact that there is no real interoperation between the server Java class and the client document as there is on the client between the Java class and the document: From the server's standpoint, once written, the element is essentially gone.)

One advantage of using the DhDocument-derived method is that you can implement an HTML template that is embedded with attributes recognized by the com.ms.wfc.html classes. By first decorating the HTML elements in the file with ID attributes and then setting the corresponding IDs in the source code using the DhElement.setBindID method, you can bind to these HTML elements, set properties on the elements, add your own inline HTML code, and so forth. This essentially allows you to code and design separate templates ahead of time and populate the template with dynamic data when the document is requested from the server.