JClass HIGrid

PreviousNextIndex

9

Sample Programs

The Sample Database  The DemoData Program  Base Example  BaseButton Example

Cell Validation Example  Row Validation Example

Exception Message Example  Popup Menu Example 


9.1 The Sample Database

The sample database that is included with JClass DataSource has the structure shown in Figure 92. This diagram shows the table names, column (field) names, and data types. Many-one relationships are shown by terminating the dotted lines connecting two tables with a black circle at the "many" end of the relationship. The key fields are shown at the top of each table and the foreign keys are designated by placing the tag "(FK)" after the data type.

Figure 82 :  Entity-Relationship diagram for the sample database.


9.2 The DemoData Program

We'll begin with an example of using a class called DemoData, used to retrieve data from a database, and then show how a HiGrid is used to display selected columns from the database.

The class performs these functions:

  1. Establishes the connection to the demo database
  2. Constructs the meta data levels and defines the queries that are used to populate the levels
  3. Adds some virtual columns to the ones based on database fields
  4. Sets the commit policies for all levels

What follows is a line-by-line breakdown of the code. Lines 1-20 are the standard copyright notice that accompanies all JClass examples. They should be assumed as the beginning lines of every other example given in this chapter. Lines 22-29 list the package name and the libraries that DemoData imports. This package identifies itself as part of the examples that accompany the product. Package datasource forms the data source part of JClass HiGrid's code. As this example shows, it is responsible for setting up the connection to the chosen database and then passing the appropriate SQL query to the database. Actually, the com.klg.jclass.datasource.jdbc package is where the code to connect via JDBC resides (or to an ODBC, through a JDBC-ODBC bridge).

Lines 54-57 define the constants that are used to specify which is the desired database connection. Line 59 states that the Microsoft Access database is currently selected.

Line 62 is the beginning of the code for the constructor. It sets up a String for the JDBC-ODBC driver, then embeds the database connection attempt in a try block. Line 74 sets up a new DataTableConnection. The JDBC URL structure is defined generally as follows:

jdbc:<subprotocol>:<subname>

In this line, jdbc is the standard base, subprotocol is the particular data source type, and subname is an additional specification that the subprotocol uses. In our example, the subprotocol is odbc. The Driver Manager uses the subprotocol to match the proper driver to a specific subprotocol. The subname identifies the name of the data source.

Line 74 begins the process of instantiating a new connection. Line 75 declares the driver. In fact, lines 74-79 are a concrete instance of a constructor call whose general form is com.klg.jclass.datasource.jdbc.DataTableConnection(String driver, String url, String user, String password, String database). Parameter driver is a String indicating which driver to load, url is the URL String described above, user is the String for the user's name, password is a String for the user's password, if required, and database is the String for the database name, which may be null. This class defines various ways of connecting to databases, such as using a host name and port, or an odbc style connection, in addition to the one used in our example. Once the connection is established, a query sets up the structure for the data that will be retrieved.

In line 108 of our example, the top-level table of our grid is declared in a query specifying that the database table, Orders, is to be used. We wish to include, as sub-tables, information contained in tables Customers, Territories, OrderDetails and Products-Categories. The last-mentioned is a detail level consisting of a join of two tables.

Line 108 shows that the MetaData class holds the structure of the query. Two constructors are used. First, the "root" constructor is called to set up and execute the query to bootstrap root levels of the DataModel and the MetaDataModel. This constructor executes the query and sets the resulting DataTable as the root of the DataTableTree. Call this constructor first, then call the MetaData(DataModel dataModel, DataTableConnection ds_connection, MetaData parent) constructor to build the meta data tree hierarchy. Next, the second form of the constructor is called to add master-detail relationships. All of this is accomplished in lines 113-125.

Note that the class' constructor does all the work, and a try block encloses all of the code. If the class can't be instantiated, the exception will print an error message on the monitor.

Once an instance of this class is successfully created, we have established a connection to the named database and the query will return a result set.

Joins are accomplished programmatically by code such as is seen in lines 116 and 124. They may be specified by using Bean customizers if you are using an IDE.

Lines 127 and following show how to attach virtual columns to a grid. These use the BaseVirtualColumn class as illustrated in line 151, 156, and 160. The type of aggregation to be done is specified using BaseVirtualColumn constants, as shown in lines 154, 158, and 162.

Finally, commit policies for each level are set, beginning at line 188. All three commit policies are illustrated.

 1  /**
 2  * Copyright (c) 2002, QUEST SOFTWARE. All Rights Reserved.
 3  * http://www.quest.com
 4  * 
 5  * This file is provided for demonstration and educational uses only.
 6  * Permission to use, copy, modify and distribute this file for
 7  * any purpose and without fee is hereby granted, provided that the
 8  * above copyright notice and this permission notice appear in all
 9  * copies, and that the name of Quest not be used in
10  * advertising or publicity pertaining to this material without the
11  * specific, prior written permission of an authorized representative
12  * of Quest.
13  * 
14  * QUEST SOFTWARE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
15  * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
16  * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. QUEST
18  * SOFTWARE WILL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY USERS AS A
19  * RESULT OF USING MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
20  * DERIVATIVES.
21  */
22  package examples.datasource.jdbc;
23
24  import com.klg.jclass.datasource.BaseVirtualColumn;
25  import com.klg.jclass.datasource.MetaDataModel;
26  
27  import com.klg.jclass.datasource.TreeData;
28  import com.klg.jclass.datasource.jdbc.DataTableConnection;
29  import com.klg.JClass DataSource.jdbc.MetaData;
30
31  /**
32  * This is an implementation of the JClass DataSource DataModel which
33  * relies on the our own JDBC wrappers (rather than IDE-specific data
34  * binding).
35  *
36  * It models a database for a fictitious bicycle company. The same
37  * schema has been implemented using an MS Access database
38  * and a SQLAnywhere database (demo.mdb and demo.db respectively).
39  * They contain the same table structures and data.
40  *
41  * The default is to use the jdbc-odbc bridge to connect to the Access
42  * implementation of the data base. You can change which data base is
43  * accessed by changing the dataBase variable to either SA or SYB below.
44  *
45  * This is the tree hierarchy for the data:
46  *  Orders
47  *    Customers
48  *      Territory
49  *    OrderDetails
50  *        Products-Categories
51  *
52  */
53  public class DemoData extends TreeData {
54
55  public static final int MS = 1;
56  public static final int SA = 2;
57  public static final int SYB = 3;
58  //Change the definition of database to any of the above constants.
59  int dataBase = MS;
60  DataTableConnection c;
61
62  public DemoData() {
63    String driver = "sun.jdbc.odbc.JdbcOdbcDriver";
64    if (System.getProperty("java.vendor").indexOf("Microsoft") != -1) {
65      // use the driver that Microsoft Internet Explorer wants
66      driver = "com.ms.jdbc.odbc.JdbcOdbcDriver";
67    }
68    try {
69      switch (dataBase) {
70      case MS:
71      // This connection uses the jdbc-odbc bridge to
72      // connect to the Access implementation of the
73      // data base.
74      c = new DataTableConnection(
75        driver,                         // driver
76        "jdbc:odbc:JClassDemo",         // url
77        "Admin",                        // user
78        "",                             // password
79        null);                          // database
80      break;
81
82      // This connection uses the jdbc-odbc bridge to connect
83      // to the SQLAnywhere implementation of the data base.
84      case SA:
85      c = new DataTableConnection(
86        "sun.jdbc.odbc.JdbcOdbcDriver",  // driver
87        "jdbc:odbc:JClassDemoSQLAnywhere",// url
88        "dba",                          // user
89        "sql",                          // password
90        null);                          // database
91      break;
92
93      // This connection uses Sybase's jConnect type 4
94      // driver to connect to the SQLAnywhere implementation
95      // of the data base.
96      case SYB:
97      c = new DataTableConnection(
98        "com.sybase.jdbc.SybDriver", // driver
99        "jdbc:sybase:Tds:localhost:1498", // url
100        "dba", // user
101        "sql", // password
102        "HiGridDemoSQLAnywhere"); // database
103      break;
104      default:
105        System.out.println("No database chosen");
106      }
107
108      // Create the Orders MetaData
109      MetaData Orders = new MetaData(this, c, " select * from Orders order by OrderID asc");
110      Orders.setDescription("Orders");
111
112      // Create the Customer MetaData
113      MetaData Customers = new MetaData(this, Orders, c);
114      Customers.setDescription("Customers");
115      Customers.setStatement("select * from Customers where CustomerID = ?");
116      Customers.joinOnParentColumn("CustomerID","CustomerID");
117      Customers.open();
118
119      // Create the Territory MetaData
120      MetaData Territory = new MetaData(this, Customers, c);
121      Territory.setDescription("Territory");
122      String t = "select TerritoryID, TerritoryName from Territories where TerritoryID = ?";
123      Territory.setStatement(t);
124      Territory.joinOnParentColumn("TerritoryID","TerritoryID");
125      Territory.open();
126
127      // Create the OrderDetails MetaData
128      // Three virtual columns are used:
129      //
130      //    TotalLessTax      (Quantity * UnitPrice)
131      //    SalesTax          (TotalLessTax * TaxRate) and
132      //    LineTotal         (TotalLessTax + SalesTax).
133      //
134      // Thus, when Quantity and/or UnitPrice is changed, these derived
135      // values reflect the changes immediately.
136      // Note 1: TaxRate is not a real column either, it is a
137      // constant returned by the sql statement.
138      // Note 2: Virtual columns can themselves be used to derive other
139      // virtual columns. They are evaluated from left to right.
140      MetaData OrderDetails = new MetaData(this, Orders, c);
141      OrderDetails.setDescription("OrderDetails");
142      String detail_query = "select OrderDetailID, OrderID, ProductID, ";
143      detail_query += " DateSold, Quantity, UnitPrice, ";
144      detail_query += " '0.15' AS TaxRate ";
145      detail_query += " from OrderDetails where OrderID = ?";
146      OrderDetails.setStatement(detail_query);
147      OrderDetails.joinOnParentColumn("OrderID","OrderID");
148      OrderDetails.open();
149
150      //Extend the row with some calculated values.
151      BaseVirtualColumn TotalLessTax = new BaseVirtualColumn(
152        "TotalLessTax",
153        java.sql.Types.FLOAT,
154        BaseVirtualColumn.PRODUCT,
155         new String[] {"Quantity", "UnitPrice"});
156      BaseVirtualColumn SalesTax = new BaseVirtualColumn(
157        "SalesTax",java.sql.Types.FLOAT,
158        BaseVirtualColumn.PRODUCT,
159        new String[] {"TotalLessTax", "TaxRate"});
160       BaseVirtualColumn LineTotal = new BaseVirtualColumn(
161        "LineTotal",java.sql.Types.FLOAT,
162        BaseVirtualColumn.SUM,
163        new String[] {"TotalLessTax", "SalesTax"});
164
165      OrderDetails.addColumn(TotalLessTax);
166      OrderDetails.addColumn(SalesTax);
167      OrderDetails.addColumn(LineTotal);
168
169      // Create the Products MetaData
170      MetaData Products = new MetaData(this, OrderDetails, c);
171      Products.setDescription("Products");
172      String query = "select a.ProductID, a.ProductDescription,a.ProductName,";
173        query += " a.CategoryID, a.UnitPrice, a.Picture, ";
174        query += " b.CategoryName";
175        query += " from Products a, Categories b";
176        query += " where a.ProductID = ?";
177        query += " and a.CategoryID = b.CategoryID";
178      Products.setStatement(query);
179      Products.joinOnParentColumn("ProductID","ProductID");
180      Products.open();
181
182      // Override the table-column associations for the Products table
183      // to exclude the Picture column so it is not included as part of
184      // the update. Precision problems cause the server to think it's
185      // changed.
186      Products.setColumnTableRelations("Products", new String[] {"ProductID", "ProductDescription", "ProductName", "CategoryID", "UnitPrice"});
187
188      // Override the default commit policy COMMIT_LEAVING_ANCESTOR
189      Orders.setCommitPolicy(MetaDataModel.COMMIT_LEAVING_RECORD);
190      OrderDetails.setCommitPolicy(MetaDataModel.COMMIT_LEAVING_ANCESTOR);
191      Customers.setCommitPolicy(MetaDataModel.COMMIT_LEAVING_ANCESTOR);
192      Products.setCommitPolicy(MetaDataModel.COMMIT_MANUALLY);
193      Territory.setCommitPolicy(MetaDataModel.COMMIT_LEAVING_ANCESTOR);
194
195    } catch (Exception e) {
196      System.out.println("DemoData failed to initialize " + e.toString());
197    }
198  }
199  
200  }

9.3 Base Example

The DemoData database connection is used for many of the examples that illustrate the use of JClass HiGrid. The first example is called BaseExample. It shows a basic grid and sets up a general framework of displaying messages above the grid to describe the particular operation currently being demonstrated in the example. It is worthwhile to describe the structure of the messaging framework, since it is reused in other examples.

BaseExample performs the following tasks:

  1. Sets up panels in a JCExitFrame.
  2. Defines methods for setting a title and a prompt.
  3. Defines a method called countChars() that is used to count the number of new lines in the passed-in prompt. The method is used in the many examples that are subclassed from BaseExample.
  4. Instantiates a grid and sets the data model with a meta data structure defined by DemoData.
  5. Uses HiGridFormatNodeListener to randomly color all cells.

Messages have a title, a prompt, and a message area that contains text found in the variable standardText. The size of the message area is determined with the help of a method called countChars. After defining the Strings for the title, prompt, and standard text, setPrompt is called. It uses countChars to determine if the text area should be reduced from its maximum size, then displays the text.

Pre-JClass 4.0 technique for traversing the format tree:

Methods setRandomRowBackgroundColor set the colors of the different types of rows in the grid, but more generally, illustrate how the grid's FormatTree is accessed and used to navigate from the root on down. Note the use of the TreeIterator class, which acts as a specialized Enumeration type to allow traversal of the format tree.

void setRandomRowBackgroundColor(boolean recordFormat) {
FormatTree formatTree = grid.getFormatTree();

  FormatNode node = (FormatNode) formatTree.getRoot();
  setRandomRowBackgroundColor(recordFormat, node);
}

void setRandomRowBackgroundColor(boolean recordFormat, FormatNode node) {
  if (node == null) {
    return;
  }
  Color randomColor = new Color(lightColor(), lightColor(),
    lightColor());
  if (recordFormat) {
    setRowBackgroundColor(node.getRecordFormat(), randomColor);
  }

  else {
    setRowBackgroundColor(node.getFooterFormat(), randomColor);
    setRowBackgroundColor(node.getBeforeDetailsFormat(), randomColor);
    setRowBackgroundColor(node.getAfterDetailsFormat(), randomColor);
  }
  TreeIterator ti = node.getIterator();
  while (ti.hasMoreElements()) {
    node = (FormatNode)ti.get();
    setRandomRowBackgroundColor(recordFormat, node);
    ti.nextElement();
  }
}

The call to lightColor returns a random value that is used to specify the RGB color value of the row.

Simpler JClass 6.0 and higher technique:

HiGrid's event delegation model lets you catch the creation of each format node as it is about to happen so that you can change the default plaf color to one that is randomly chosen.

class BaseExampleHiGridFormatNodeListener extends
  HiGridFormatNodeAdapter {
  public void beforeCreateFormatNodeContents(HiGridFormatNodeEvent event) {
    CellStyleModel cellStyle = grid.getRecordCellStyle();
    Color randomColor = new Color(lightColor(), lightColor(), lightColor());
    cellStyle.setBackground(randomColor);
  }

Turning off repainting

Whenever a grid is first loaded, it's a good idea to turn off repainting. Therefore, a common code idiom is:

grid.setBatched(true);
  if (applet == null) {
  grid.setDataModel(new jclass.datasource.examples.jdbc.DemoData());
  if (grid.getDataModel() == null) {
    grid.setDataModel(
      new jclass.datasource.examples.vector.VectorData());
  }
  }
  else {
    grid.setDataModel(new jclass.datasource.examples.vector.VectorData());
  }
  setRandomRowBackgroundColor(true);
grid.setBatched(false);

The operations that could cause repaint flickers are bracketed by setBatched(true) ... setBatched(false).


9.4 BaseButton Example

This example merely adds a button to the bottom of the BaseExample's frame. It extends BaseExample and its button responds to actionPerformed events.

public class BaseButtonExample extends BaseExample
implements ActionListener {

The button is used in subsequent examples to initiate changes to the grid.


9.5 Cell Validation Example

The interface for doing cell-level validation is illustrated here.

public class CellValidationExample extends BaseExample implements
  HiGridValidateListener

Because this class implements the HiGridListener interface, it must define methods stateIsInvalid, valueChangedBegin, and valueChangedEnd. The class also uses the validation capabilities of jclass.cell to fire a stateIsInvalid message if the validation criteria are not met. If the change meets the validation criteria, a message on the standard output reports the changed information. If not, the application refuses to change the current row until valid data is placed in the cell being edited.

Here is how valueChangedBegin validates the cell's contents:

/*
 * HiGridValidateListener implementation
 */
/**
 * Invoked just before the data source value of the field is updated.
 */
public void valueChangedBegin(HiGridValidateEvent e) {
  JCValidateEvent event = e.getValidateEvent();
  RowNode rowNode = e.getRowNode();
  // find out the user-defined name for this level

  //(assume it is unique)
  String levelName = rowNode.getDataTableModel().getMetaData().getDescription();
  String oldValue = getStringValue(event.getOldValue());
  String newValue = getStringValue(event.getValue());
  // reject any changes where the new entry is empty
  if (newValue.length() == 0) {
    event.setValid(false);
    return;
  }
  // only do validation for top-level PurchaseOrderNumber column
  if (levelName.compareTo("Orders") == 0 &&
    e.getColumn().compareTo("PurchaseOrderNumber") == 0) {
    // reject any changes where the first character changes

    boolean valid = (oldValue.length() > 0) &&
      (newValue.length() > 0) &&
      (oldValue.charAt(0) == newValue.charAt(0));
    event.setValid(valid);
  }
}

Method setValid determines whether stateIsInvalid will be fired or not.


9.6 Row Validation Example

There are times when you must simultaneously validate more than a single cell. There may be additional dependencies between the cells in a row that must be checked before committing changes to the database. The current example is based on the premise that an item cannot be delivered before it is ordered.

package examples.higrid.validation;

import java.awt.*;
import java.awt.event.*;
import java.util.Date;

import javax.swing.*;
import com.klg.jclass.datasource.*;
import com.klg.jclass.higrid.*;
import com.klg.jclass.util.swing.JCMessageHelper;
import com.klg.jclass.util.swing.JCExitFrame;

import examples.higrid.BaseExample;

/**
 * Do row level validation in HiGrid.
 */
public class RowValidationExample extends BaseExample {

public RowValidationExample() {
  super();
  grid.getDataModel().addDataModelListener(
new RowValidateDataModelListener());
  setTitle("Row Validation");
  setPrompt(usage);
}

private Frame myFrame;

public void setFrame(Frame f) {
  myFrame = f;
}

static String usage = "This example shows how to do row level
  validation.\n" +
    "It will reject changes where the RequiredDate comes before
the OrderDate.";
void validateRow(DataModelEvent event, RowNode rowNode) {
  // find out the user-defined name for this level (assume it is
  //   unique)
  DataTableModel model = rowNode.getDataTableModel();
  String levelName = model.getMetaData().getDescription();
  // only do validation for top-level columns
  if (levelName.compareTo("Orders") == 0) {
    long bookmark = rowNode.getBookmark();
    // ensure that RequiredDate is after OrderDate
    try {
      Date orderDate = (Date)model.getResultData(bookmark,
"OrderDate");
      Date requiredDate = (Date)model.getResultData(bookmark,
"RequiredDate");
      if (orderDate.after(requiredDate)) {
        event.cancelProposedAction();
        JCMessageHelper.showError("Row Validation",
          "OrderDate must come before
             RequiredDate");
      }
    }
    catch (Exception e) {
    }
  }
}

class RowValidateDataModelListener extends DataModelAdapter {
  public void beforeMoveToCurrentRow(DataModelEvent e) {
    validateRow(e, grid.getCurrentRowNode());
  }

  public void beforeCommitRow(DataModelEvent e) {
    validateRow(e, grid.getRowTree().findRecordRowNode(null,
      e.getBookmark()));
  }
}

public static void main(String args[]) throws InterruptedException {
  JCExitFrame f = new JCExitFrame("Row Validation Example");
   RowValidationExample app = new RowValidationExample();
  app.setFrame(f);
  f.getContentPane().add(app);
  f.pack();
  f.show();
  javax.swing.FocusManager.getCurrentManager().focusNextComponent(
    app.getGrid());
}

}

9.7 Exception Message Example

The examples.higrid.exception.ExceptionMessageExample.java lists all the HiGrid and DataSource event constants defined in com.klg.jclass.higrid.HiGridEvent and com.klg.jclass.datasource.DataModelEvent. It presents two ways of handling events. By default, HiGrid presents event and exception messages in a dialog window. You can provide your own mechanism by implementing the HiGrid listener interface and providing code there to deal with the event. Similarly, to deal with DataModel events, you implement the DataModelListener interface.

To use your own event handling routine, have your class implement the HiGridListener interface and register itself as a HiGridListener:

grid.addHiGridListener(this);
grid.getErrorHandler().setShowErrorDialog(false);

Customized event handling is illustrated by displaying the type of event in the text area using the messaging mechanism defined in Section 9.3, Base Example.


9.8 Popup Menu Example

JClass HiGrid's popup menu can be changed, or completely replaced by one of your own design. The example shows that the short and long forms of the popup menu can be selected using the constants EditPopupMenu.DEFAULT_SHORT_POPUPMENU_LIST and EditPopupMenu.DEFAULT_LONG_POPUPMENU_LIST. It goes on to show how to install additional choices; specifically, an "about" choice that, when clicked, presents a message in the applet's text area.

To view the code, see examples.higrid.menu.PopupMenuExample.java.


PreviousNextIndex