Programmer's Guide



Chapter 12   Using HTML-Based Views

HTML data view controls let you display data from one or more database tables in a tabular (row and column) format. This chapter introduces the HTML data view. It covers the following topics:

About HTML data views   Top of page

SilverStream's HTML data view is a specialized HTML table whose cells can contain a combination of data-bound and non-databound controls. You can bind the controls to database columns, display images or other in-memory data. SilverStream uses the AgpViewPanel class to represent an HTML data view.

Unlike its counterpart in the View Designer, the HTML data view does not support hierarchical drill-down. However, you can create the effect of hierarchical navigation by coordinating the display of several data views inside a page.

    For an example of a master/detail application that links multiple data views in a page, see Application Techniques>HTML Client Techniques in the help system: Building a Master/Detail Page.

View components   Top of page

A data view consists of two rows. The first row is called the header row. The header row contains static text for column headers. The second row is called the repeating or data row. You normally place controls (usually label controls) in the repeating row to display data in the body of the data view. This row is called the repeating row because SilverStream dynamically evaluates the controls at runtime, and repeats the row as many times as necessary to display the data. The following diagram shows the HTML data view at design time and at runtime.

At design time, you can examine and modify the properties of the data view and its labels using the Property Inspector.

The HTML data view and data   Top of page

You can bind an HTML data view to any data source that implements the AgiRowCursor interface for data navigation and the AgiRowSetManager interface for data updates.

You can bind the data at design time using the Property Inspector or dynamically at runtime.

Binding to a data source at design time

In the Property Inspector for a data view, you can bind the view to a database table or a data source business object (DSO).

To bind to a database table, you select From Table in the Load Data dropdown and select the table in the Table Name dropdown. When you bind to a database table, you can restrict the data for the view by checking the Distinct checkbox, specifying a Link clause, or limiting the number of rows returned for the view.

To bind to a DSO, you select From Business Object in the Load Data dropdown and specify the DSO in the Business Object Name edit box. The options for restricting the data are not supported when you bind a data view to a DSO.

Binding to a data source programmatically

Most of the time, you will want to bind your HTML data views at design time. However, SilverStream does give you the option to bind at runtime as well. At runtime, you can bind an HTML data view to an AgpData object by calling the setDataProvider() method or to a DSO by calling the setDataSource() method. You would typically do this once in the PageLoaded event. For example, this code changes the data source to an AgpData object named agpData3.

  View1.setDataProvider(agpData3); 

When using this method, controls in the data view must refer only to fields within the specified data source. Also, SilverStream does not recommend programmatically scrolling this data source outside of the data view as it will affect the data view's behavior.

The setDataSource() method lets you dynamically associate a DSO with a data view. For example, this statement binds a data view to the data source object called "mydso" in the com.dso package.

  View1.setDataSource("com.dso.mydso"); 

If you bind a data view to an AgpData that gets data from a DSO, you need to call the invokeQuery() method on the AgpData object to initialize the view. Similarly, if you bind a data view directly to a DSO, you need to call the invokeQuery() method on AgpViewPanel. The invokeQuery() method allows you to initialize any arguments required by the DSO. To force the view to query again programmatically, or to change the query and order-by parameters, call query().

If you bind a data view to a data source dynamically, you also need to bind the controls in the view's detail row dynamically. Rather than using the Property Inspector to bind the controls at design time, you call agDataMgr.bind() at runtime to accomplish the same goal. The agDataMgr object is an instance variable provided on every page. You typically bind controls in a view's detail row either directly to the view or to an AgpData object on the page. The following code associates a DSO with a data view and binds three labels in the view's detail row to columns in the DSO:

  View1.setDataSource("dsoEmployee"); 
agDataMgr.bind(Label1, "Value", "getText", "setText", View1, new
   Integer (0));
agDataMgr.bind(Label2, "Value", "getText", "setText", View1, new
   Integer (1));
agDataMgr.bind(Label3, "Value", "getText", "setText", View1, new
   Integer (2));

Working with data views, pages, and data   Top of page

When working with a data view, you can restrict the data that is displayed and link it to another control's data. You can also specify when the data view is loaded.

Restricting the data

You can restrict the set of rows that display in the data view in these ways:

Checking the Distinct checkbox

You can filter out duplications by checking the Distinct checkbox in the Property Inspector. When this checkbox is checked, only one copy of each row appears in the dataset, even if duplicates of it are present in the underlying database.

Calling the query() method at runtime

Call the query() method to restrict the dataset at runtime. For example:

  View1.query("employees.deptid=300"); 

Both the Where clause and the query() method restrict the set of rows retrieved. SilverStream appends queries that are set programmatically to the Where clause specified in the data view's property sheet using the AND operator.

The query() method provides additional flexibility in determining what data the data view displays, but it has these restrictions:

Using a Link clause

You can restrict the rows that are displayed in the data view by making them dependent on data displayed by another control on the page. For example, you might display a customer name in a text field and that customer's orders in a data view. When the customer field changes, the displayed orders also change. You can ensure that this happens by specifying a Link clause in the data view's property sheet. The Link clause expression describes a join operation between the datasets of the page and the Data View. You might think of the Link clause as a refinement of the data view's Where clause.

SilverStream automatically generates the Link clause, if possible, as you specify the data properties of the data view. If SilverStream cannot determine a relationship between the datasets, it does not generate a Link clause. You can use the Property Inspector to add a Link clause or change an existing Link clause.

The window for entering a Link clause looks like this.

You can also use the Link clause to specify selection criteria for a data view. In this case, the expression you enter filters the data in the same way that a Where clause for an agpData object does. For example, to narrow the dataset to include only employees of department 300, you could specify this expression:

  employees.deptid=300 

Link clauses are not supported for views bound to DSOs.

Specifying the maximum number of rows returned

In the Property Inspector, you can specify the maximum number of rows that will be returned for a data view. To do this, you need to check the Limit Number of Rows Returned checkbox and specify the number of rows in the Maximum Rows edit box.

Specifying when the data is loaded

Regardless of how you specify the data view's dataset and its dependencies, you must decide when to populate it with data.

If you want to load data to the data view when the page is loaded in the browser, check the Automatic Query checkbox on the data view's property sheet. When Automatic Query is true (checked), the data view is populated when the page is loaded.

If Automatic Query is not checked, the data view does not populate until a call to the query() method or refreshRows() method is made.

Updating the database   Top of page

You can choose the scope of updating data on a data view. The choice that you make depends on the requirements of your application. You can update changes just for the data view or for the entire page.

Using data view methods   Top of page

You can programmatically navigate the rows, manipulate the data, and control the visual elements of the data view (such as limiting the number of rows and whether scrolling is emulated in the browser).

The AgiRowCursor interface supplies methods to navigate and manipulate the data view's dataset. The AgiRowCursor is a pointer that you use to navigate or manipulate rows of data.

To get the data view's AgiRowCursor, call either getCurrentRowCursor() or getRootRowCursor(). For example:

  AgiRowCursor rc = View1.getCurrentRowCursor(); 
AgiRowCursor rc = View1.getRootRowCursor();

Both methods return an object of type AgiRowCursor. The differences between the methods are the following:

Navigating data

Using the AgiRowCursor for the data view, you can navigate the rows in the dataset with these methods:

Navigating an AgiRowCursor retrieved from a view does not affect the view's display.

Manipulating data

You can manipulate the individual properties and rows within a data view. In addition, you can perform operations on the entire data set.

Use the AgiRowCursor for the data view to manipulate properties in the current row with these methods:

You can perform operations on entire rows with these methods:

Data views also support methods that let you manipulate an entire dataset. These methods are available on the AgiRowSetManager interface. Since AgpViewPanel implements this interface, you can call these methods on the data view itself (not on its AgiRowCursor):

Limiting the number of rows

By default, the number of rows displayed at one time in the data view is 10. You can change this at design time in the Property Inspector. In addition, you can change it programmatically by calling one of the following methods.

Method

Description

setLimitRowCount()

Specifies whether to limit the number of rows shown at one time.

setPageRowCount()

Sets the number of rows to be displayed at one time, if the number is being limited.

setOverlapRowCount()

Specifies the number of rows to overlap when scrolling up and down by a page.

Use the get..() forms of these methods to find out the current settings.

Controlling Scrolling

The data view is actually implemented as a table in the HTML output to the browser. SilverStream emulates scrolling for a data view with image buttons for scrolling up and down. You can turn the default scrolling off as well as scroll the data programmatically, perhaps to support your own implementation of scrolling. This table lists the methods you use to control scrolling.

Method

Description

setEnableScrolling()

Specifies whether to use the default scrolling emulation.

scrollToFirstRow()

Scrolls the view to the top row of data.

scrollToLastRow()

Scrolls the view so that the last row of data is visible.

scrollUpRow()

Scrolls the view up by one row.

scrollDownRow()

Scrolls the view down by one row.

scrollUpPage()

Scrolls to the previous page of data.

scrollDownPage()

Scrolls to the next page of data.

Use getEnableScrolling() to find out whether the default scrolling mechanism is being used.

The scrollUpPage() and scrollDownPage() methods (as well as the scroll buttons presented to the user) take into account the page overlap count specified with the setOverlapRowCount().

Adding editable controls bound to data   Top of page

Data views can contain editable controls, allowing the user to modify data. You can bind the editable controls to database columns through the Property Inspector. Some examples of editable controls are text fields and checkboxes. In the following example, the user can type "AM" or "PM" into a text field.

Adding editable controls not bound to data   Top of page

You might require an editable control in a data view that is not bound to a database column. For example, a text input field could track the quantity of a product that a customer wants to purchase. Or, in the following example, a checkbox indicates the selection of one or more rows in a data view.

The checkbox control must maintain a selection status for each row; however, this is not a value you typically store in the database. A data view requires that all of its controls get their values from the same data source, so this situation requires some special handling.

You have three options for creating temporary data storage to hold the values for this type of editable control. You could add an extra column to the database table, create a DSO that includes a dummy expression column, or use row-level processing to manipulate the unbound control.

Row-level formatting   Top of page

The HTML data view provides an event called rowGenerateBegin that is fired just before SilverStream generates the HTML for each data row of the view. This event gives you an opportunity to format each row individually.

The rowGenerateBegin event supplies a single argument, an AgoEvent object. The source in the event object is the view itself. The argument in the event object is an AgpTag object corresponding to the <TR> tag at the beginning of the repeated view row. This tag object is supplied to allow the application programmer to set row-level properties. Properties of view cells, text, and other controls can be set by marking these objects as programmable in the property inspector and calling methods on them directly. (Note that many earlier versions of browsers do not support properties on <TR> tags. The same functionality can generally be achieved using cell properties.) The application can get the AgiRowCursor object for the row about to be generated by calling the getCurrentRowCursor() method on the view.

Keep in mind that attributes of controls in the view's repeating row are not automatically reset between the generation of subsequent rows. This means that if the application changes the background color of a row cell in the rowGenerateBegin event for a certain row, this change persists in the rows that follow it until the application changes the color again. For this reason, most applications should be written not to make any assumptions about the state of a control or cell in the rowGenerateBegin event, but instead always set the appropriate properties for that row.

The rowGenerateBegin event allows for various kinds of row-level formatting. By adding code for this event, you can:

Alternating row colors   Top of page

If you create a large data view with many rows, it might be helpful to the user to alternate the colors of the rows to make it easier to read the data or to associate the data in the left-hand columns with the data in the right-hand columns.

To alternate background colors in each row:

  1. Declare a boolean variable in the declarations section of the page:

      private boolean m_green = false; 
  2. In the rowGenerateBegin event on the view, use the boolean variable to decide the background color of the row:

           AgpTag trTag = (AgpTag) evt.getArgument(); 
    if (m_green)
    trTag.setProperty("BGCOLOR", "#F0FFF0"); // green
    else
    trTag.setProperty("BGCOLOR", "#FFFFFF"); // white
    // Invert the boolean variable for the next row.
    m_green = !m_green;
  3. To ensure that the first row is always white, you can reset the m_green flag to false in the pageGenerateBegin event.

Handling browsers that do not support <TR> tags

If your users have browsers that do not support properties on the <TR> tag, you can mark all cells in the detail row as programmable and set their background colors instead of using the <TR> tag argument of the event as shown here:

  String color = "#FFFFFF";  // white 
if (m_green)
   color = "#F0FFF0"; // green
Cell1.setProperty("BGCOLOR", color);
Cell2.setProperty("BGCOLOR", color);
Cell3.setProperty("BGCOLOR", color);
// etc.
// Invert the boolean variable for the next row.
m_green = !m_green;

Adding data-driven formatting   Top of page

You might want to control the formatting of a row based on the data that appear in that row. For example, you might to highlight new products using a special icon; you might want to display negative numbers in red, or show delinquent accounts with a different background color from other rows. You can achieve these effects using the getCurrentRowCursor() method on the view in the rowGenerateBegin event as shown here:

  AgiRowCursor c = View1.getCurrentRowCursor(); 
// Set the color of a text span containing profit/loss information
double profit = ((Double)
   c.getProperty("Accounts.ProfitLoss")).doubleValue();
if (profit < 0)
   spanProfitLoss.setProperty("COLOR", "#FF8080"); // red
else
   spanProfitLoss.setProperty("COLOR", "#000000"); // black
// Set the background color of cells for delinquent accounts
Date dueDate = (Date) c.getProperty("Accounts.DueDate");
// Compute whether the account is delinquent
boolean delinquent = ...
if (delinquent)
{
   Cell1.setProperty("BGCOLOR", "#A080A0"); // purple
   Cell2.setProperty("BGCOLOR", "#A080A0");
}
else
{
   Cell1.removeProperty("BGCOLOR"); // default
   Cell2.removeProperty("BGCOLOR");
}

As an alternative, you can use images that vary based on row data using an image expression set in the Property Inspector without any programming; such an expression might look like this:

  if Products.IsNewProduct 
   then "../Objectstore/Images/ProductNew.gif"
   else "../ObjectStore/Images/ProductNormal.gif" endif

Manipulating unbound controls   Top of page

One of the powerful features of HTML data views is that the data they display is loaded from a database table or AgpData object. Controls in a view can be bound to values from this data source. HTML data views support both read-only controls (labels, images) and read/write controls (text fields, drop-down lists, etc.). Suppose, however, that the application needs some controls in the view loaded from a database table and some controls loaded only from in-memory storage used by the application. An example is a view displaying a list of products along with an editable text field control for the number of each product to be ordered by the end user. The text field cannot be bound to a field in the products table, because the order quantity for a particular end user is not a column in the table. The values cannot simply be read out of the control because there is only a single control in the server-side page object, but multiple controls (one per row) in the generated HTML.

The rowGenerateBegin event provides a mechanism to manipulate unbound controls. While the page is generating its HTML, an application can use the rowGenerateBegin event to populate an unbound control from in-memory storage. While the page is processing POSTed values from the browser, the application can use the control's valueChanged event to read new values and store them into the in-memory storage.

For example, in the declarations section of the page, initialize a HashMap to store product order quantity values:

  private HashMap m_quantity = new HashMap(); 

In the rowGenerateBegin event for the view, get the value for the text field control that corresponds to the row being generated and store it into the control:

  AgiRowCursor c = View1.getCurrentRowCursor(); 
Object productID = c.getProperty("products.productid");
String value = m_quantity.get(productID);
txfldQuantity.setText(value);

In the valueChanged event of the txfldQuantity control, get the new value and store it back into the HashMap. Note that it is perfectly correct for an application to use the getCurrentRowCursor() method on a view during event processing for a control in the view:

  String newValue = (String) evt.getNewValue(); 
AgiRowCursor c = View1.getCurrentRowCursor();
Object productID = c.getProperty("products.productid");
m_quantity.put(productID, newValue);

Then, in processing the pageActionPerformed event for the Order button (or equivalent), the application can read the quantities ordered from the m_quantity HashMap. This technique can be used for any number of unbound controls in a view.

Grouping records by value   Top of page

You can use HTML data views to group data rows by some value; for example, you might group products by product category. When displaying grouped data, applications typically want to display the grouping value only on the first row in which it occurs. This is easily accomplished using the rowGenerateBegin event because the values of data-bound controls are set before the rowGenerateBegin event is fired. This allows the application to change (or clear) the values of controls as part of the HTML generation of the row.

To group records by value:

  1. Add an order-by clause to the view to sort by product category:

      products.categoryid Ascending 
  2. Place a label control in the first column of the view and bind it to an expression describing the category:

      products.product_categories.categoryname 
  3. Declare a member variable in the declarations section of the page:

      private String lastRowValue; 
  4. In the pageGenerateBegin event of the page, clear the variable:

      lastRowValue = null; 
  5. In the rowGenerateBegin event, compare the value of the label control with the variable. If it is the same, clear the label so that no text displays except on the first instance of that value:

      String labelValue = lblCategory.getText(); 
    if (labelValue != null && labelValue.equals(lastRowValue))
       lblCategory.setText(null);
    lastRowValue = labelValue;

Including additional rows by inserting raw HTML   Top of page

You can implement additional grouping capabilities in an HTML data view by inserting raw HTML that includes additional rows. One common use for this technique is to generate subtotals for the rows in each group. For example, in a view that displays a list of products, you might use this technique to generate subtotals for each product category.

To include additional rows by inserting raw HTML:

  1. Add a control that allows for raw HTML (either an HTML control or a label control) inside the first or last cell of a view row.

    The control appears inside the table cell and inside the table row in the generated HTML.

      <TR>  beginning of the row 
       <TD>  beginning of cell one
             contents of cell one
       </TD>  end of cell one
       <TD>  beginning of cell two
             contents of cell two
             inserted HTML (contents of raw HTML control)
       </TD>  end of cell two
    </TR>  end of the row
  2. Set the control to allow for raw HTML.

    To set an HTML control to provide raw HTML, you need to call the setEnableHTMLParsing() and setEmbedHTMLInTable() methods, passing a value of false to each:

      HTMLControl1.setEnableHTMLParsing(false); 
    HTMLControl1.setEmbedHTMLInTable(false);

    To set a label control to provide raw HTML, you need to call the setRawHTMLMode() method, passing a value of true:

      Label1.setRawHTMLMode(true); 
  3. Add code to the rowGenerateBegin event that inserts HTML.

    The raw HTML control must close the cell and row in which it is contained, open a new row, and fill in the desired contents. For example:

      </TD></TR><TR><TD><!-- skip first cell --></TD><TD>Total: 10 

    The raw HTML control should not close its final cell (by inserting </TD> or its row (by inserting </TR>). These closing tags are inserted automatically as part of normal row generation in a view.

    The biggest challenge in inserting extra rows in a view is determining when to do so. To do this, the code in the rowGenerateBegin must examine the current row and determine if this row is the last row in the group. This can be accomplished by getting the row cursor, advancing it to the next row, and checking to see whether that row belongs to the next group. Note that you can freely navigate the row cursor for a view without affecting the display of data. The following example illustrates how this is done:

      AgiRowCursor c = View1.getCurrentRowCursor(); 
    Object thisCategory = c.getProperty("products.categoryid");
    boolean endOfGroup = false;

    groupTotal++;
    if (!c.gotoNext())
       endOfGroup = true; //last row in the view
    else
    {
       Object nextCategory = c.getProperty("products.categoryid");
       if (!thisCategory.equals(nextCategory))
          endOfGroup = true;
    }
    if (!endOfGroup)
       Label4.setText(null);
    else
    {
       //Optionally compute additional row information here...
       Label4.setText("</TD></TR>
          <TR><TD></TD><TD>Total:" + groupTotal);
       groupTotal = 0;
    }

    This code presumes that the page has a member variable called groupTotal:

      private int groupTotal; 

    This variable is set to zero in the PageGenerateBegin event:

      groupTotal = 0; 

Creating reusable objects for row formatting   Top of page

The most useful place to write code that handles row-level formatting is in the rowGenerateBegin event. The rowGenerateBegin event is part of a listener interface called AgiHTMLViewListener.

If you want to create re-usable objects to handle row formatting, you can create an object that also implements the AgiHTMLViewListener interface. You then register it with any view using the view's addAgiHTMLViewListener() method.






Copyright © 2000, SilverStream Software, Inc. All rights reserved.