Chapter 10 Paging and customizing Tables

Abstract

This chapter describes the behaviour of the PagingControl and the oppertunity to customize Tables using the Tablecustomizer in an OpenXMA Dsl project.

To use the PagingControl in a page means to define the table in your pml-model as pageable:

First you define the table that you want to use with the PagingControl with the attribute pageable=true and a style attribute with a valid name like style=myPagingStyle.

  table customerTable key=customers.oid pageable=true style=myPagingStyle top=customerTableHeader bottom=100% {
                customers.firstName    width=80
                customers.lastName     width=140
                customers.birthDate    width=140
                customers.emailAddress width=140
            }

Next, you define the style (myPagingStyle in this example) where you choose which navigation elements to be visible on the control. In this example we want to show all navigation elements:

 style myPagingStyle {   
    paging-position : top
    paging-style : custom
    paging-jump-size : 10
    paging-page-size : 10 max = 1000
    paging-controls : back, customize, pagesize, next, fastnext, fastback, info, reload, start, end
}

Assuming you intend to display some data in your table, meaning loading data initially when the page becomes first visible, you can do this in the eventmapping block:

 command loadData 

 eventmapping {
    onEnter -> loadData

}   

This instructs the generator to generate code that calls a loadData remoteCall when the page becomes visible.

Additionally you need a place where to react on reload Events which are triggered from the UI navigation elements of the paging control:

 command loadData 

 eventmapping {
    onEnter -> loadData
    customerTable.onLoad -> loadData
}   

The above statement (customerTable.onLoad -> loadData) does exactly this. Every UI Event triggered from the pagingControl navigation elements results in a remote call to "loadData". So in this example an abstract method loadData is generated on the server side and has to be implemented to handle the backend logic.

This is all you have to do for displaying the pagingControl on your table. If you generate this pml you find on the client side the following generated code:

 public void createModels(

 ...

 customerTable_pagingControl = new PagingWMClient((short) 4,this,customerTable)
     customerTable_pagingControl.setPageSize((short)10);
     customerTable_pagingControl.setJumpSize((short)10);
     customerTable_pagingControl.setPagingListener(new PagingListener() {
         public void reload(int offset, int pagesize) {
             loadData(null);
         }
    });

 ...

 protected void loadData(SelectionEvent event) {
     newRemoteCall("loadData").execute();
 }
 ...

The loadData Implementation on the server side could look like this

@Override
public void loadData(RemoteCall call, RemoteReply reply) {
  int offset = customerTable_pagingControl.getOffset();
  short pageSize = customerTable_pagingControl.getPageSize();
  short sortingColumn = customerTable_pagingControl.getSortingColumn();
  boolean isAscending = customerTable_pagingControl.getAscending();
  // fetch your data according to these paging control parameters
  ...
}
 

In Conjunction with the PagingControl you can optionally also use the TableCustomizer that addresses the following issues:


In the figure above the dialog shows the following settings:

  • all columns are visible

  • there is a filter set on the "First name" column

  • "First name" appears as the third column in the table (seen from left to right)

  • the table rows are sorted first ascending by last name, then descending by first name

Filters are set by the filters dialog. For each column of a valid type (as listed above), the user can define a filter. A filter on a column is an expression. A filter expression can be either made up of AND-subexpressions or OR-subexpressions. If there exist more than one column with an filter, the filter expressions are joined with the AND operator.

Example. The user wants to a list of persons whose first name is equal to ("John" or "Frank") AND whose birthdate is before 1.1.1985:

The user can also choose to allow NULL rows to be valid.

All this can also be modeld in dsl. There is a little more information needed in your .pml file. To enhance our previous example with the tableCustomizer you would have to update the above modeling file in the following way:

style pagingTable {
    table-customizer : testTableCustomizer
    paging-customizer-image : "/org/openxma/demo/customer/xma/client/cog_go.png"
    paging-position : top
    paging-style : custom
    paging-jump-size : 10
    paging-page-size : 10 max = 1000
    paging-controls : back, customize, pagesize, next, fastnext, fastback, info, reload, start, end
}

tablecustomizer testTableCustomizer instancetype org.openxma.addons.ui.^table.customizer.^xma.client.ITableCustomizer

The new lines are marked bold: First you define a unique name for the tableCustomizer. Next, optionally you can specify an image that is used on the pagingControl for the button used to invoke the tableCustomizer dialog. Of course the image has to exist in your projects artifact in the specified path. And last you have to specify which TableCustomizer to use for the pagingControl.

The implementation classes for the customizer interface are not included in the default project classpath. So if you want tu use the tableCustomizer the projects classpath and your build must be extended.

Using the TableCustomizer means that queries used at the backend to display data can change at runtime, depending from the filter settings applied in the TableCustomizer Dialog. So these "filterable queries" have to be defined in the persistance model (dml), since queries are not filterable by default.

You can define filterable queries like this:

repository CustomerDao for Customer {
    table = "T_CUSTOMER"
    operation Customer[] findCustomers() : from Customer as customer
}

service CustomerDas {
    CustomerDao.crud
    CustomerDao.findCustomers 
     filter=true 
}

Caution: Do not use filter=true on operations that already have a order by-clause! This can lead to erroneous HQL-queries. Initialize the sort order in the presentation layer, if you want to predefine the sort order of the table data.

In the above example the findCustomers() Query defines an operation that is extended with the filter keyword. So all information is available to link the filter query with the TableCustomizer. In our Customer example the generated code on the server side looks like this:

public QueryObject getCustomerTableQueryObject() {
         int offset = customerTable_pagingControl.getOffset();
         short pageSize = customerTable_pagingControl.getPageSize();
         short sortingColumn = customerTable_pagingControl.getSortingColumn();
         boolean isAscending = customerTable_pagingControl.getAscending();
         XMASessionServer xmaSessionServer = (XMASessionServer)XMASessionServer.getXMASession();
         ITableCustomizerServer tableCustomizer = (ITableCustomizerServer)xmaSessionServer.getComponent(Short.valueOf(customerTable_customizerComponentId.getEncodedValue()));
         QueryObject filterQueryForTable = null;
         if (sortingColumn >-1) {
             filterQueryForTable = tableCustomizer.getQueryObject(sortingColumn,isAscending);
         } else {
             filterQueryForTable = tableCustomizer.getQueryObject();
         }
         filterQueryForTable.setFirstResult(offset);
         filterQueryForTable.setMaxResults(pageSize);
         return filterQueryForTable;
}

As you can see, the code uses the paging control information: offset, pageSize, and sorting information is read to set up dynamically a generic filter query for the CustomerTable. The returned QueryObject is actually the filter that has to be passed to the query as parameter. The invokation of the query itself has to be done manually and the best place to do so is the remote method that was defined in the model to react on the UI navigation events:

@Override
public void loadData(RemoteCall call, RemoteReply reply) { 
 Collection<CustomerView> findCustomers = null;
 QueryObject queryObject = getCustomerTableQueryObject();
 findCustomers = getTypedComponent().customerDas.findCustomers(queryObject);
 getTypedComponent().setCustomers(findCustomers);
 customerTableFill();
}

In the above sample implementation for the loadData remote call, the previously defined filter query "findCustomers" is invoked and the filter that was retrieved from the generated method getCustomerTableQueryObject() is passed as parameter.

For runtime, the Table Customizer code has to be merged into the applications's war:

<!-- Ant Build File -->

<target name="preconditions">
  <property name="table_ui_addon_war" value="${XMA}/org.openxma.addons.ui.table/4_0_1/org.openxma.addons.ui.table_4.0.1.war"/>
  <property name="table_ui_addon_jar" value="${XMA}/org.openxma.addons.ui.table/4_0_1/org.openxma.addons.ui.table-facade_4.0.1.jar"/>
  ...

  

<target name="webapp" depends="createManifest, common_jars, component-jars">

  <copy file="${epclient_jar}" preservelastmodified="true" todir="${webappDir}/WEB-INF/lib"/>
  
  <!-- this is our working directory to assemble the xma webapplication -->
  <mkdir dir="${webappDir}" />

  <!-- For XMA Table Customizer -->
  <unjar dest="${webappDir}" overwrite="true" src="${table_ui_addon_war}"/>
  ...

<path id="project.class.path">
  <pathelement location="${table_ui_addon_jar}"/>
  >pathelement location="${platform_client_jar}"/>
  ...

Also the xma-app.xml has to be adopted:

   <component name="TableCustomizerComp" implPackage="org.openxma.addons.ui.table.customizer.xma" >
   <resource href="components/tablecustomizercompclient.jar" type="jar" version="442fdcce3fcbe65fa3546fcfa1e8e25c" locale="de_AT" />
   <resourceLink href="clientrt/xmartclient.jar" />
   <resourceLink href="clientrt/epbase.jar" />
</component>

<resource href="components/addoncommonclient.jar" type="jar" version="838b1ea95de0db7bfc2805314bc08fcb" />

In the applicationContext you have to add

  <context:component-scan base-package="org.openxma.addons.ui.table"/>
  ..
  <value>classpath:org/openxma/addons/ui/table/customizer/**/*.hbm.xml%lt;/value>
  
  

A common requirement for projects is the ability to export the data into a file. You can configure the paging control to provide an additional button that handles the export (only available for clients running on the Microsoft Windows platform).


The configuration can be expressend in the style information for the paging control with the export-keyword:

style pagingTable {
    table-customizer : testTableCustomizer
    paging-customizer-image : "/org/openxma/demo/customer/xma/client/cog_go.png"
    paging-position : top
    paging-style : custom
    paging-jump-size : 10  
    paging-page-size : 10 max = 1000
    paging-controls : back, customize, pagesize, export, next, fastnext, fastback, info, reload, start, end
}
If used with the tableCustomizer, the export keyword does more than just showing an additional button. The code that is needed to handle the export is generated also. The exporter uses additional classes that are not included in a standard openXMA Dsl project. See the needed configuration details for the export utility. Notice that the generated code for handling the export is only provided if the tableCustomizer is used. For the export functionality without the tableCustomizer the export-implementation has to be provided manually.

On export, the CSV-File will be saved to the default directory for temporary files (using the default encoding of the client VM) and opened with the default handler associated with CSV-Files. Be aware, that encoding problems may occur in Microsoft Excel if the default encoding is different to windows-1252!

In the serverside page there is an abstract method generated that must be implemented manually. The implementor is asked here to provide the collection that is used as input for the .csv table export. The name of the abstract method is built based on the table name: queryFor<tableName>ExportData(QueryObject queryObject). Take care to provide the right collection - it should correlate with the filter query used to display the table data:

@Override
public Collection<?> queryForCustomerTableExportData(QueryObject queryObject) {
    return getTypedComponent().customerDas.findCustomers(queryObject);
}
By default all table data is requested for export in one call to queryFor<tableName>ExportData(QueryObject queryObject). To prevent huge amounts of data to be transferred in a single server request, the data set can be split up into smaller chunks. The maximum number of rows to be transferred at once can be specified in a client page override, e.g.:
@Override
protected int getCustomerTableExportBatchSize() {
    return 1000;
}
The client will continuously read chunks with the given number of rows until all data is transmitted. The generated default implementation gives 0, which means no split-up transmission.

Observe: Be sure to comply with the given boundries in QueryObject at server side, or endless loops may occur.