TableFactory Use

Description

This note describes use of TableFactory when developing code. In case you want to modify the TableFactory class itself, you are recommended to browse the wiki pages for Trac Tickets concerning changes to TableFactory, where you can follow its development and find motivations for some of the design choices.

The code for TableFactory is found in gui/table/TableFactory.java in client/servlet/.

TableFactory Features

The TableFactory class was developed as a simple and consistent way to display data stored in the database. Its basic features are:

  • It takes a class and an ItemQuery for data of that class as input, and can then create a Table item with the data resulting from the query.
  • Columns are created for variables corresponding to getter accessor methods, including those inherited from parent classes.
  • Columns are only created generically for getter methods with simple return types.
  • For special classes, other columns may be created for which is set a reader object of a class implementing the AttributeReader interface (props/AttributeReader.java in client/servlet/). If you want to use this option, it is probably simplest to look at some example in the TableFactory code (start at public method void useColumnsFromClass()).
  • If the query produces more rows than fits on a single page (limit given be getMaxResults()), a table for the first page is created, extended with links for accessing other pages of data. The latter functionality is managed by a Scroller object (gui/Scroller.java in client/servlet/).

As TableFactory is extensively used because of its flexibility in creating tables, this also introduces dependencies between displayed tables and the classes they represent, as well as TableFactory settings itself. Extending a class with a new getter method might introduce a new undesired column in all created tables for the class. TableFactory can be modified to counteract this, but then care should be taken that the changed functionality does not affect other tables (see section Caution).

Example of Use

Before going into the details of TableFactory features, it may be helpful to see an example of TableFactory use. Assume that you have a class FunnyStuff extending BasicItem, and you want to display a table of its database contents in action class ViewTableExample:

 public Class ViewTableExample
  extends ProteiosAction<ViewTableExample>
 {
  ...
  public void runMe()
  {
    ...
    // Get table for FunnyStuff
    TableFactory tableFactory = getTableFactory();
    tableFactory.reset();
    tableFactory.setItemClass(FunnyStuff.class);
    ItemQuery<FunnyStuff> query = FunnyStuff.getQuery();
    // Optional restrictions for query
    ...
    tableFactory.setQuery(query);
    Table stuffTable = tableFactory.build();
    stuffTable.setTitle("Very Funny Stuff");
    // Layout
    String formTitle = getLocale().get("Example of Table created with TableFactory");
    RowLayout layout = getLayoutFactory().getRowLayout();
    layout.add(new Title(formTitle));
    layout.add(stuffTable);
    setLayout(layout);
  }
 }

Some parts of the example are optional:

Query
If no query is set, TableFactory will create one for the input class. However, it is not uncommon that you want to set extra restrictions on the query, in which case you have to construct it and set it in TableFactory.
Table Title
TableFactory creates a default table title from the class name, by using a simple procedure to set it in plural (in most cases it tries to add an 's', which in the example above would result in "FunnyStuffs"). Here it is replaced with the title "Very Funny Stuff" (the title "Example of Table created with TableFactory", defined later in the example, will be displayed at the top of the form, and is not part of the table).

Generic Table Creation with TableFactory

Given the input class, TableFactory uses Java Reflection to find information on available methods.

Elements of Created Table

Title Row

Table title, by default created from the input class name, by using a simple procedure to set it in plural.

Header Row

A row with column names, by default created from getter method names with prefix "get" or "is" removed (for details, see section Getter Method Name).

Data Rows

One row of data for each item resulting from the query.

Initial Selection Checkbox Column

If the input class is assignable from the Identifiable class (core/Identifiable.java in api/core/), which is the normal case, an extra initial checkbox column is added to the header and data rows. The cells in this column are not used to display data for the class, but to select the item corresponding to the table row for use by other actions. The cell for this extra header column is coupled to an action that toggles the checkbox settings for the displayed table page.

Footer Panel

Panel with information such as the total number of items resulting from the query. In case the number exceeds the maximum number to show on one page, links to other data pages for the table are also placed in this section.

Column Creation From Getter Method

Getter Method Name

It is assumed that getter methods have names starting with "get" or "is". Beware of boolean or Boolean getter methods with names starting with e.g. "has", as these will not result in creation of a column. If you still find the latter type of method name the most natural in your class, you can add an extra getter method for the variable in question with name starting with "get" or "is", in order for a column to be provided by TableFactory.

The method name without prefix "get" or "is" will be used as the string key for the column. The column key is used in TableFactory to identify the column, e.g. for specifying a special column order, or that a column should be hidden. Only one column with a specific name will be created (having both getter methods for boolean/Boolean variables with method names starting with "get" and "is" will only result in one column).

The column key is also used as key in localization dictionary files to find the header name for the column (if no entry exists, the key is used as name). Adding or changing the name of a getter method in a class used by TableFactory, may therefore require changes in the TableFactory class itself, and all localization dictionary files.

Getter Method Return Type

For a column to be created for a getter method, its return type must be one of int, Integer, float, Float, double, Double, boolean, Boolean, String, or Date.

Modifications of Table Creation

Reasons for Modifying the Table Creation

While the generically created table in many cases can be used as is, it is also common that there is a desire to modify the table. Some reasons for the latter are:

  • You want to add a column for a value that cannot be retrieved by a simple getter method, e.g. an annotation.
  • Some of the generated columns correspond to uninteresting variables for the current purpose.
  • Some of the generated column names may not be as clear as possible.
  • The order of the columns may be illogical or unpractical. An example of the latter is when a large description column is placed early, making it necessary to scroll a long distance in order to inspect the columns to the right of it.
  • You may want the user to be able to filter the basic query, before showing results.
  • You may want the user to be able to sort the results after the values in a selected column.
  • The initial selection checkbox column may be undesired for some tables.

In order to make TableFactory more useful, extra functionality has been added in different ways:

  • Hard-coded Modifications
    It is often found that desired table modifications are specific to the input class. The modifications are here hard-coded into TableFactory as special cases for the classes in question.
  • Configuration Parameters
    Some features of TableFactory can be configured by setting instance variables to desired values. Examples are setMaxResults(int maxResults) for max number of items to display on a single page (default is 20, 0 will result in only one page) and setSelectCheckBoxColumnHidden(boolean selectCheckBoxColumnHidden) for hiding the initial selection checkbox column. Most configuration values are reset to default values by calling reset() in TableFactory,
  • User-controlled Settings
    The user can control some features via values of instance variables in TableFactory. The values of these variables have to be transferred to the new TableFactory instance, before the new table is built. The latter is implemented for page selection, user selected filtering and sorting.

Caution

Any change in functionality that is hard-coded into TableFactory might affect all tables created by it. If a change in TableFactory is considered in order to fix a problem with a table created for a specific class, try to restrict the change to the class in question. Also remember that tables for a specific class might appear on several displayed forms, and that features that are undesired in some circumstances may be essential in other. Examples include extra columns, renaming of columns, and ordering of columns. In these cases you have to decide what is most important, that a feature is present in one form, or absent in another.

Post-Modification of Tables Created by TableFactory

As a last resort, you might consider modifying the table created by TableFactory, before it is displayed. While this is sometimes a solution, care should be taken to not assume too much about the structure of the table, such as included columns, as it is vulnerable to changes in TableFactory. As an example of this use, the standard file table includes directories. The table created by TableFactory for File items is used as base, and rows from a table created for relevant Directory items are added to the former. Before being added to the file table, the directory table rows are modified by insertion of empty cells for file table columns without correspondence in the directory table, and removal of columns unique to directories. Also, the selection checkbox is replaced by an empty cell and the directory name appended with a slash to indicate its directory status. For details, see private method void prependDirectories(Table table, Directory dir, Boolean select) in action/directory/ViewActiveDirectory.java in client/servlet/. This practice of combining two tables created by TableFactory is normally not recommended, as changes in TableFactory may result in two tables that are incompatible, although each table by itself is completely consistent.

Examples of Modifications of Table Creation

If you want to modify table creation for a new input class, you need to change the TableFactory class. If you just want to add existing functionality to a new input class, you may only have to add the class to a conditional checking what classes the functionality should be used for. In any case, inspection of the TableFactory class is recommended here. The following list may be helpful in finding the appropriate methods to inspect/modify:

Add a Column that Cannot be Generated from Getter Method
Check out public method void useColumnsFromClass().
Hide an uninteresting Column
Check out private method void initializeHiddenColumnList().
Rename Displayed Column Name
Check out private method void initializeColumnNameToUse().
Change Order of Displayed Columns
Check out private method void initializeColumnSortList(). Note that if you use this option, column keys for all columns that should be displayed must be added to the 'columnOrder' list. If you use the option to rename displayed columns, the column key to add to the list is the column key before renaming.
Add User-Selected Filtering
Check out private method void initializeColumnFilters(List<Column<?>> columnList), especially the beginning where the value of boolean instance variable 'filterOptionAvailable' is set based on the input class.
Add User-Selected Sorting
Check out private method void initializeSortOption(), especially the beginning where the value of boolean instance variable 'sortOptionAvailable' is set based on the input class. Also note that you may have to modify private method void initializeColumnKeyToUse(), in case the variable name obtained from the getter method by removing the "get" or "is" prefix and setting the first character to lower case does not equal the variable name used in the class. If you want to set a default sorting to be used, you should define a List<String> defSortKeyList and HashMap?<String, String> defSortTypeHashMap, fill them with appropriate values, after which you add them to your TableFactory instance with tableFactory.setDefSortKeyList(defSortKeyList) and tableFactory.setDefSortTypeHashMap(defSortTypeHashMap) (for an example, see action/hit/ListHits.java in client/servlet).
Remove Initial Checkbox Column
Configure your TableFactory instance with tableFactory.setSelectCheckBoxColumnHidden(true).