Abstract
This chapter describes the behaviour of the PagingControl and the oppertunity to customize Tables using the Tablecustomizer in an OpenXMA Dsl project.
The PagingControl addresses the issue of displaying large tables in an efficient and user friendly manner. The UI is provided with the following navigation controls - listed in the order as they appear in the UI from left to right - see also the sample screenshot:
First: navigates back to the first page. Deactivated if page is already the first page.
Fastback: navigates jumpSize pages back. If less pages than jumpSize exist, the first page is shown. The Button is deactivated on the first page.
Back (Previous) Navigates one page back if the current page is not the first page.
Reload: triggers a reload event on the pagingControl with the current offset
Next: Navigates one page forward.
FastForward: navigates jumpSize pages forward. If less pages than jumpSize exist, the last page is shown. The Button is deactivated on the last page.
Last: Jumps to the last page. Deactivated if the total Resultsize ist unknown.
Pagesize (Combo): User can define the page size by changing the value of this combo. The page ize is the row count that is displayed at once in the table.
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:
allow user to define which columns of the table are visible
allow user to define the sort order and sort direction of the table data
allow user to define the order in which the columns appear in the table
allow user to define filters for columns of type Numbers, Strings, Dates, I18Enumeration Domains
allow user to persist their 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:


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.
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.
Since the filter settings for the TableCustomizer can also be persisted in the database, a datamodel definition is needed.
The DDL file for the TableCustomizer can be retrieved from the TableCustomizer project itself for the Oracle DB. If used with other DB Vendors, the DDL has to be adapted according to the vendor specific SQL language:
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.
First the classpath of the project must be extended with the openCSV lib - currently version 2.0 is used.
The lib must also be included for the build and provided at the client - example for the ant build.xml file and also all the generated DTO (Data Transfer Object) - classes that are used to fill the Table (simple POJOs) are needed on the client side :
<property name="openCSV" value="${INFRA}/opencsv/2_0/opencsv-2.0.jar"/>
...
<path id="project.class.path">
...
<pathelement location="${openCSV}"/>
</path>
<target name="webapp" depends="common_jars,component-jars">
..
<copy file="${openCSV}" preservelastmodified="true" tofile="${webappDir}/clientrt/openCSV.jar"/>
<xmachecksum file="${webappDir}/clientrt/openCSV.jar"/>
..
</target>
<target depends="compile" name="common_jars">
<fileset dir="${classDir}" id="commonclient_fileset">
...
..
.
<include name="org/openxma/demo/customer/**/dto/*.class"/>
</fileset>
</target>
The library must be declared also as a resource in the app-descriptor file (xma-app.xml)
<xma_application> ... .. . <resource href="clientrt/openCSV.jar" type="jar" version="" /> ... .. . </xma_application>