Chapter 6. Lists and Query Browsing

6.1. Introduction

A common task in web applications is displaying tables. This is a simple task if the tables are small and do not contain a lot of rows. The whole table can be visible at once and there is no need to split the data into pages as well as provide an option to order the table by new column or display a search form that can be used to filter the data. In such cases where these features must be available, Aranea provides widget org.araneaframework.uilib.list.ListWidget and some support classes. In this chapter we will introduce these widgets and supporting API and show how to use and extend them.

ListWidget is used to define the list columns, the way the list can be filtered and ordered, how many items are shown per pages etc. ListWidget uses org.araneaframework.uilib.list.dataprovider.ListDataProvider to get the current list items range that also match with the current filter and order conditions. ListDataProvider can cache whole table and provide the ListWidget with the appropriate item range or fetch only the specific item range from database. Aranea provides two implementations:

org.araneaframework.uilib.list.dataprovider.MemoryBasedListDataProvider
This is the memory-based solution that must be provided with the whole data at once. It does filtering , ordering and paging memory-based. The data source is not restricted here. This is a very fast and easy-to-use solution for tables with few (typically less than 100) rows. This is a good solution when your server has enough memory to store all the rows, and quering all the rows does not take much time.
org.araneaframework.uilib.list.dataprovider.BackendListDataProvider
This is the database solution that will cache only the current item range and executes a new database query each time a filtering , ordering or paging conditions change. This is a powerful solution for tables with more than 500 rows. Especially, when the query is complex and takes time (so it would not be useful to query all the rows at once).

6.2. Lists API

6.2.1. A Typical List

A typical list will be created used like this:

...
private BeanListWidget myList;
...
protected void init() {
  ...
  myList = new BeanListWidget(MyModel.class);

  myList.setOrderableByDefault(true);
  myList.addField("name", "#Name").like();
  myList.addField("surname", "#Surname").like();
  myList.addField("phone", "#Phone no").like();
  myList.addField("birthdate", "#Birthdate").range();
  ...
  myList.setInitialOrder("name", true);
  myList.setListDataProvider(new MyListDataProvider());  
  addWidget("myList", myList);
  ...
}
...

Note that here we basically do following things:

Create the list
The line new BeanListWidget(MyModel.class) creates a new list widget that is associated with the JavaBean model class MyModel .
Make fields orderable
The line myList.setOrderableByDefault(true) configures the following fields as orderable.
Add list fields
The line myList.addField("name", "#Name").like() adds a field associated with the JavaBean property "name" (this is also the identifier of the field), with a label "Name", makes the field filterable by Like filter.
Set the initial list order
The line myList.setInitialOrder("name", true) sets the list to be ordered by field "name" by default.
Set the list data provider
The line myList.setListDataProvider(new MyListDataProvider()) sets the data provider for the list.
Register the list
The line addWidget("myList", myList) initializes and registers the list allowing it to function.

Now that we have created the list we show how to build a simple data provider. The following example code should be in the same widget as the previous:

...
private class MyMemoryBasedListDataProvider extends MemoryBasedListDataProvider {
  protected MyMemoryBasedListDataProvider() {
    super(MyModel.class);
  }
  public List loadData() throws Exception {             
    return lookupMyService().findAllMyModel();
  }
}
...

The line super(MyModel.class) associated this MemoryBasedListDataProvider with the JavaBean model class MyModel. The method List loadData() implements loading all table rows and returning it as a List object.

Later, we will also discover using org.araneaframework.uilib.list.dataprovider.BackendListDataProvider.

6.2.2. Fields

As the list may be displayed as a table, it is basically an ordered collection of items. In the previous example, we defined a list of MyModel.class typed items that have fields 'name', 'surname', 'phone' and 'birthdate'. By listing of MyModel.class, we also told ListWidget the corresponding field types, e.g String.class, String.class, Long.class and Date.class. In fact this feature of reflection is the only distinction between the ListWidget and BeanListWidget.

Each list field have its own Id, label and type. The labels are used to automatically create a corresponding title row above the displayed table. The types are used to describe how to order or filter the whole data using this field. E.g String.class is treated differently than other types, because usually one would prefer to order by this field ignoring the case. Both the labels and types are also used to build a corresponding search form - an automatically built FormWidget - for the list.

If we would like to add some list fields that are not MyModel.class fields, we can pass it's type to the ListWidget like following:

myList.addField("income", "#Income", BigDecimal.class);

Here the myList could be just a ListWidget rather than a BeanListWidget.

When adding a list field, we can also provide this field-related ordering and filtering information.

6.2.3. Ordering

Each list field can be orderable or not. We already discovered ListWidget's method setOrderableByDefault(boolean) that switch whether to configure fields that are added afterwards orderable or not. This method can be used several times in the list configuration.

Another way is to set each field individually orderable or not when they are added to the list. In such case add additional boolean argument to the addField() method such as:

myList.addField("phone", "#Phone no", false);

Notice the false as third parameter. true means that the list can be ordered by this field and false means the opposite. By not providing this parameter, simply the last value is used which has been set by setOrderableByDefault(boolean) method.

In addition, we already used method setInitialOrder(String, boolean). It sets a specified field (the first argument) to be ordered by default. true as the second argument tells the ordering should be ascending, false would mean descending. By not providing this information, the list is displayed in the original order.

6.2.4. Filtering

Filtering means that we only display a certain list items. The list can be filtered using its fields and data provided by the search form of this list.

For this, we must provide the ListWidget with the corresponding org.araneaframework.uilib.list.structure.ListFilters and FormElements. As the form elements are dummy "boxes" that hold search data, each ListFilter is related to a certain filter test, e.g. equality, greater than comparison etc. Each ListFilter also knows what information it must consider. In general, one list field is compared against a value provided by the search form. It's also assumed that a blank search field means that this particular ListWidget is currently disabled.

Fortunately, in most cases it's unnecessary to add these search fields manually. Instead, if one is adding a list field, he or she can assign both the ListFilter and FormElement for this field very simply:

myList.addField("address", "#Address").like();

Here we simply add an 'Address' field providing it with label and telling there's should be a Like filter for this field. By this, we automatically add a TextControl into the search form. By filling it with value 'Paris', we will see only rows which 'Address' field contain 'Paris', 'paris', 'PARIS' etc.

To describe, how this works, we show a longer version of the previous code:

myList.addField("address", "#Address");
myList.getFilterHelper().like("address");

So there's a special class org.araneaframework.uilib.list.structure.filter.FilterHelper that is used to add list filters. All ListWidget.addField() methods just return a little different version of this helper class, called a FieldFilterHelper. It's methods do not need a field Id and thus make one not to repeat the same field Id for each filter. In general, the shorter usage is recommended of course. However some filters are more complicated and may be related to more than one list field. For those, one must use the FilterHelper instead.

By default all filters that deal with the Strings are case insensitive. To configure some filters to be different, use the following:

myList.addField("country", "#Country").setIgnoreCase(false).like();
myList.addField("city", "#City").like();
myList.addField("address", "#Address").setIgnoreCase(true).like();

This can be explained following: Before adding a Like filter for the 'country' field, we switched to the case sensitive mode. And before adding a filter for the 'address' field, we switched to the case insensitive mode. Thus the city's filter is case sensitive as the country's but the address' filter does ignore the case.

This state is held by the FilterHelper and can be modified either by calling a method of the FilterHelper or the FieldFilterHelper. In such way, the following parameters can be set:

Case sensitivity
By using setIgnoreCase(boolean) one assigns new filters to ignore case (default) or not. This applies to filters that use String comparison.
Strict/non-strict
By using setStrict(boolean) one assigns new filters to disallow equality or not. By default equality is not allowed (strict). This applies to filters such as GreaterThan, LowerThan, Range etc.

Sometimes tables need to contain a column (or more) that is not bound to specific model object field. One can add such a column to the list structure like this:

myList.addEmptyField("choose", "#Choose");

The column still must have unique ID (e.g. "choose" in this case). The label for the column is optional. In addition, this column would not be orderable as its values are not controlled by the ListWdiget. However, this column can be used for check boxes, radio buttons, links, etc.

Now, let's show which filters we have got:

FilterHelper methodListFilter classDescription
eq()EqualFilterTests if the value of a certain list field is equal to the value of a certain search form field. The filter is disabled if the search field is blank.
eqConst()EqualFilterTests if the value of a certain list field is equal to a certain constant. This filter is always enabled.
gt(), lt()GreaterThanFilter, LowerThanFilterTests if the value of a certain list field is greater than (lower than) the value of a certain search form field. This filter is disabled if the search field is blank.
gtConst(), ltConst()GreaterThanFilter, LowerThanFilterTests if the value of a certain list field is greater than (lower than) a certain constant. This filter, if used, is always enabled.
like()LikeFilterTests if the pattern in a certain search form field matches with the value of a certain list field. This corresponds to the LIKE expression in SQL with some modifications. By default, it takes '%' and '*' symbols as any-string wildcards and '_', '.' and '?' as any-symbol wildcards. In addition, the pattern does not have to match with the whole string ('%' is automatically added before and after the pattern string). The wildcards and their automatic adding is configured by the org.araneaframework.uilib.list.util.like.LikeConfiguration which is found from the Aranea org.araneaframework.uilib.ConfigurationContext. This filter is identical in memory-based and database backend usage. This filter is disabled if the search field is blank.
likeConst()LikeFilterTests if a certain constant pattern matches with the value of a certain list field. This filter is always enabled.
startsWith(), endsWith()LikeFilterThis is very similar to the like() constraint, and tests if the list field value either starts or ends with the user-provided pattern. The filter is disabled if the search field is blank.
startsWithConst(), endsWithConst()LikeFilterTests if given constant pattern is either in the beginning or in the end of the field value. This filter, if used, is always enabled.
isNull(), notNull()NullFilterTests if the value of a certain list field is null (is not null) if the value of a certain search form field equals to a specified value.
isNullConst(), notNullConst()NullFilterTests if the value of a certain list field is null (is not null). This filter is always enabled.
range()RangeFilterTests if the value of a certain list field is between two values of certain search form fields. The filter is identical to the greater than or lower than filter in case of one of the search fields is blank. This filter is disabled if both search fields are blank.
fieldRangeInValueRange(), valueRangeInFieldRange(), overlapRange()RangeInRangeFilterTests if two values of certain list fields are between two values of certain search form fields, vice-versa or do they have a non-empty intersection. This filter is disabled if both search fields are blank.
in()InFilterTests if the value of a list field is among the values of a MultiSelectControl. It does a case-sensitive search for this.
sqlFunction()SqlFunctionFilterTests if the value returned from a certain SQL function is equal (or is greater than or is lower than) to the value of a certain list field, search form field or a constant. The arguments of the SQL function can also be chosen among the values of list fields, search form fields and constants. This filter cannot be used memory-based. This filter is always enabled.

By default the FormElements added into the search form have the same identifiers as the list fields. Therefore there can be only one search field per list field. If one would like to override the used Id for FormElement, any filter could be added like following:


        myList.addField("country", "#Country").like();
        myList.getFilterHelper().like("country", "anotherCountry");

The first line adds a list field 'country' and a Like filter associated with it as well as a new FormElement with Id of 'country'. The second line adds another Like filter associated to the list field 'country' and a new FormElement with Id of 'anotherCountry'.

By adding a filter, the corresponding FormElement is automatically created and added to the search form of the list. Now we cover the properties of the few FormElement describing their default values and showing how to customize them:

PropertyDefault valueCustomizing
IdSame as the list field Id.Call addField(...).<filter>("myCustomId");
LabelSame as the label of the associated list field.Call addField(...).useCustomLabel("myCustomLabel").xxx(...);
ControlIs selected considering the type of the associated list field:
TypeControl
java.lang.String TextControl
java.math.BigInteger, java.lang.Long, java.lang.Integer, java.lang.Short, java.lang.Byte NumberControl
java.math.BigDecimal, java.lang.Double, java.lang.Float FloatControl
Other subclasses of java.lang.Number FloatControl
java.util.Date, java.sql.Date DateControl
java.sql.Time TimeControl
java.sql.Timestamp DateTimeControl
java.lang.Boolean CheckboxControl
All others TextControl
Call addField(...).xxx(new MyCustomControl());
DataCorresponds to the type of the associated list field.Call addField(...).useFieldType(MyType.class).xxx(...);
Initial valueAlways null to disable the filter by default.After adding the field and the filter call myList.getForm().setValueByFullName("fieldId", customInitialValue); or add a custom Formelement.
MandatoryAlways false as all search conditions are optional.After adding the field and the filter call myList.getForm().getElementByFullName("fieldId").setMandatory(true); or add a custom Formelement.
FormElementSee all properties above.To use a custom Formelement, call addField(...).xxx(new MyCustomFormElement(...));. To disable adding it at all, call addField(...)._xxx(); (notice the underscore).

The xxx marks any filter adding method. As one can count, there are 6 overridden methods for each list filter: 2 versions for providing a custom Id or not and 3 versions for providing a custom FormElement, Control or neither of them. In addition there are methods that start with an _ for disabling adding a form element. Using the FilterHelper instead of FieldFilterHelper is analogous except all filter adding methods take the list field Id as the first argument in addition.

It's import to notice that xxxxConst methods do not create a form element because they are independent of the search form at all - they are constant. However they can actually take a value Id for the defined constants as well. These Ids can be used later to convert specific values when creating a database query. Of course non-constant filters have the same Ids but just use them mainly to get values from the search form. xxxxConst filters have 2 overridden add methods depending on whether the custom value Id is provided or not. By default it's the same as the field Id.

6.2.5. Backend Data Provider

Now that we have demonstrated defining lists and also creating MemoryBasedListDataProvider, we will discover using BackendListDataProvider. The following example code should be in the same widget as constructing of the related ListWidget.

...
private class MyBackendListDataProvider extends BackendListDataProvider {
  public MyBackendListDataProvider() {
    super(true);
  }
  protected ListItemsData getItemRange(ListQuery query) throws Exception {
    return lookupMyService().findMyModel(query);
  }
}
...

The line super(true) constructs BackendListDataProvider with cache enabled (only used when there are no change in query). Notice that there is no association with any JavaBean class here. The method ListItemsData getItemRange(ListQuery query) implements loading current item range according to the range indexes and filtering and ordering conditions. org.araneaframework.backend.list.mode.ListQuery and org.araneaframework.backend.list.mode.ListItemsData may be thought as being input and output of each list data query.

ListQuery is a simple JavaBean that holds the following properties:

List structure (since 1.1)
The structure of the list contains all the list fields and static information about the filtering and ordering. (It is constructed once as the ListWidget is defined.)
List item range indexes
This is 0-based start index and items count ( Long objects) that define the range. By default, lists are shown by pages. Although all items can be shown at once also. Then the start index is zero and items count is omitted.
Filter and order info (since 1.1)
These contain the current filter and ordering data as instances of Map and OrderInfo.
Filter and order expressions
These could be thought as an abstraction of SQL expressions which are constructed using the info described above (even the same instances). These expressions will be used in the WHERE and ORDER BY clauses.

Generally, this whole object is just passed to org.araneaframework.backend.list.helper.ListSqlHelper class that is used to generate SQL statements and fetching the results from database. Latter is hold in ListItemsData object which is a simple JavaBean that holds the following properties:

List items range
Model objects that are the result of the query .
Total count
Total count ( Long object) of the list. This is important information for navigating through the whole list. Notice that this depends only on filtering conditions.

Notice that BackendListDataProvider actually do not depend on using databases. It just provides a simple query object and expects a simple result to be returned. Thus, you have the power to use it as you like. At the same, Aranea provides a very useful class org.araneaframework.backend.list.helper.ListSqlHelper that generate SQL statements and fetches the results from database. We strongly recommend it together with its subclasses that support different database systems. Currently Oracle (OracleListSqlHelper), Postgre (PostgreListSqlHelper), and HSQL (HsqlListSqlHelper) databases are supported (they are used similarly because they all extend ListSqlHelper).

The following example discovers the simplest usage of ListSqlHelper. The following code should be in a service class instead of previously discovered Widget:

public class MyService {
  ...
  private DataSource dataSource;
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.addMapping("name", "NAME");
    helper.addMapping("surname", "SURNAME");
    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

Method ListItemsData findMyModel(ListQuery query) does the following:

Constructs and initializes the helper
The line ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query) constructs OracleListSqlHelper - an Oracle specific ListSqlHelper - and passes it the DataSource and ListQuery data.
Adds column mappings
The line helper.addMapping("name", "NAME") defines that identifier of column "name" will be converted into "NAME" when used in an SQL statement. There may be lot of differnece between JavaBean properties names and database fields names. The same database identifier ( "NAME" ) is used when fetching data from ResultSet by default. This could also have another identifier set by providing it as the third argument.
Provides the helper with a simple SQL query
The line helper.setSimpleSqlQuery("PERSON") sets the whole SQL query with parameters using only the given database table name. Filtering and ordering is added automatically according to the ListQuery data.
Executes the query and retrieve the data
The line return helper.execute(MyModel.class) executes and retrieves data of both total count and items range queries. The ResultSet is read using the default BeanResultReader .

The following example discovers the custom usage of ListSqlHelper.

public class MyService {
  ...
  private DataSource dataSource;
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.addMapping("name", "NAME");
    helper.addMapping("surname", "SURNAME");
    helper.addMapping("phone", "PHONE_NO");

    StringBuffer s = new StringBuffer();
    s.append("SELECT ");
    s.append(helper.getDatabaseFields());
    s.append(" FROM PERSONS");
    s.append(helper.getDatabaseFilterWith(" WHERE ", ""));
    s.append(helper.getDatabaseOrderWith(" ORDER BY ", ""));

    helper.setSqlQuery(s.toString());
    helper.addStatementParams(helper.getDatabaseFilterParams());
    helper.addStatementParams(helper.getDatabaseOrderParams());

    return helper.execute(MyModel.class);
  }
  ...
}

Method ListItemsData findMyModel(ListQuery query) does the following:

Constructs and initializes the helper
The line ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query) constructs OracleListSqlHelper - an Oracle specific ListSqlHelper - and passes it the DataSource and ListQuery data.
Adds column mappings
The line helper.addMapping("name", "NAME") defines that identifier of column "name" will be converted into "NAME" when used in an SQL statement. There may be lot of differnece between JavaBean properties names and database fields names. The same database identifier ( "NAME" ) is used when fetching data from ResultSet by default. This could also have another identifier set by providing it as the third argument.
Gets SQL substrings from the helper
The line helper.getDatabaseFields() returns just the comma-separated list of database column identifiers that were just defined in the mapping. This does not depend on the original set of list columns at all. The line helper.getDatabaseFilterWith(" WHERE ", "") returns the WHERE clause body with the provided prefix and suffix. It returns an empty string if there is no filter condition currently set (it does not mean there are no filters defined). Notice that we only deal with SQL strings here. As ListSqlHelper uses PreparedStatement objects to execute queries, there must be provided statement parameters in addition to the SQL string. This generally provides better performance of executing similar queries.
Constructs SQL query string
StringBuffer is used to construct the whole SQL query string. Notice that the helper does not construct it totally by itself. This lends user more power for complex queries. It is very important that the constructed query is for getting all rows that match with the current filter and order conditions, but not the range conditions. ListSqlHelper always executes two queries: one for getting the items count and another for getting the items range. Generally, both of these can be easily constructed from this one provided query. This implementation depends on the database system and therefore the concrete ListSqlHelper subclass.
Gets SQL parameters from the helper
The line helper.getDatabaseFilterParams() returns SQL parameters of WHERE clause or empty list if there are none.
Provides the helper with the SQL query
The line helper.setSqlQuery(...) sets the SQL string and the line helper.addStatementParams(...) adds the query parameters ( ListSqlHelper uses PreparedStatement s). Of course, the order of parameters must match with the SQL string.
Executes the query and retrieve the data
The line return helper.execute(MyModel.class) executes and retrieves data of both total count and items range queries. The ResultSet is read using the default BeanResultReader.

ListSqlHelper mappings and converters

All Aranea List filters that are propagated with values from the filter form construct an expression chain. This chain is built each time any condition is changed. E.g if one is searching for persons whose birthday is between July 6th, 1950 and Sept 2nd, 1990 then there's one value 'Birthday' and two values 'July 6th, 1950' and 'Sept 2nd, 1990' which have 'Birthday_start' and 'Birthday_end' as names. Ordering the list is done the same. When retrieving data from database all these information must be considered to build an appropriate query. Therefore all these variables must be mapped to database fields. When reading the query results Bean fields must be mapped to ResultSet columns. In general, these Bean fields match exactly with the variables. But considering more specific cases, they are not assumed to be the same.

The following list covers the terms that are used when configuring ListSqlHelper:

List field
Each list has a set of fields (or columns) that are displayed. All fields are listed up in the SELECT clause. Some of them can be used for filtering and ordering as well. Field name can be e.g "birthday" or "group.name".
Expression value
Values are the temporary information in the list filtering. '1980-08-21' is a value. 'Birthday_start' is a name of that value. In simple cases one list field matches with one value. In case of the range filter two different values (start and end of the range) are used. Also one value can be used together with two or more fields. A value identifier is used for optional converting before using it in a query. This is done by adding a Converter object to ListSqlHelper. E.g. booleans have to be converted into numeric (0 or 1) values.
Database column
Database column can be for example 'age' or 'company.name' as well as 'MAX(price)' or '(SELECT(COUNT(*) FROM document WHERE userId = user.id)' (an inner SELECT) - any expression that is part of a SQL string.
Database column alias
Database field alias is for example 'name', 'total_price' etc. It's just an identifier not a whole expression. In ListSqlHelper one can assign an alias for each database field or have it automatically generated. The result of a query is a table - a ResultSet - which columns have the same names as the aliases in the query. An alias can also be used in a custom filter condition (WHERE clause) to identify the same database field or expression that was added in the SELECT clause.

ListSqlHelper methods for configuring mappings:

MethodPurpose
addMapping(String fieldName, String columnName, String columnAlias) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT and is read from the ResultSet.
addMapping(String fieldName, String columnName) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT and is read from the ResultSet. The corresponding column alias is generated automatically.
addDatabaseFieldMapping(String fieldName, String columnName, String columnAlias) Adds a field name to database column name and column alias mapping. A given field is listed in the SELECT but is not read from the ResultSet.
addDatabaseFieldMapping(String fieldName, String columnName) Adds a field name to database column name mapping. A given field is listed in the SELECT but is not read from the ResultSet. The corresponding column alias is generated automatically.
addResultSetMapping(String fieldName, String columnAlias) Adds a field name to database column alias mapping. A given field is not listed in the SELECT but is read from the ResultSet.

ListSqlHelper methods for configuring converters:

MethodPurpose
addDatabaseFieldConverter(String value, Converter converter)Adds converter for expression value.
addResultSetDeconverterForBeanField(String beanField, Converter converter)Adds deconverter for ResultSet column by list field that is mapped with that Column.
addResultSetDeconverterForColumn(String rsColumn, Converter converter)Adds deconverter for ResultSet column.

ListSqlHelper naming strategies

Since Aranea MVC 1.1 ListSqlHelper also support naming strategies. This means that one do not need to define database column names and aliases for all list fields. Instead only list fields are listed up and they can be transformed into database column names and aliases using a strategy.

A strategy is defined by the following interface. NamingStrategy.

public interface NamingStrategy {
        String fieldToColumnName(String fieldName);
        String fieldToColumnAlias(String fieldName);
}

To set or get a strategy use methods ListSqlHelper.setNamingStrategy(NamingStrategy namingStrategy) or ListSqlHelper.getNamingStrategy() respectfully.

The standard implementation StandardNamingStrategy adds underscores to all names (e.g. "firstName" -> "first_name"). For an alias all dots are converted into underscores (e.g. "parent.friend.age" -> "parent_friend_age"). For a name all dots except the last are converted into underscores (e.g. "parent.friend.age" -> "parent_friend.age", so "parent_friend" is expected to be a table alias).

If one wishes to define table aliases for the naming strategy PrefixMapNamingStrategy (enabled by default) can be used. By using method addPrefix(String fieldNamePrefix, String columnNamePrefix) one can add a custom prefix for database columns and aliases. An instance of PrefixMapNamingStrategy can be retrieved by method ListSqlHelper.getPrefixMapNamingStrategy().

As naming strategies still expect a set of list fields to be defined there is a way to add list fields without any mappings.

A set of fields are provided by following interface.

public interface Fields {
        Collection getNames();
        Collection getResultSetNames();
}

To set or get a fields provider use methods ListSqlHelper.setFields(Fields fields) or ListSqlHelper.getFields() respectfully.

A standard implementation StandardFields enables to add fields using the following methods.

MethodPurpose
addField(String field)Adds a field by its name.
addFields(String[] fields)Adds a set of fields by their names.
addFields(Collection fields)Adds a set of fields by their names.
addFields(Class beanClass)Adds all the fields of the Bean class.
addFields(ListStructure structure)Adds all the fields defined in the list structure.

There are also corresponding methods to add fields using a prefix and methods to remove the fields (using a prefix or not).

To get the StandardFields call ListSqlHelper.getStandardFields().

The following example shows how to just list up the fields (the corresponding column names and aliases are generated by the naming strategy). Because the column "phone" has a "non-standard" column name, it is set separately.

public class MyService {
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    StandardFields fields = helper.getStandardFields();
    fields.addField("name");
    fields.addField("surname");

    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

If all fields described for the ListWidget should be used they can be added using the ListStructure contained in the ListQuery:

public class MyService {
  ...
  public ListItemsData findMyModel(ListQuery query) {
    ListSqlHelper helper = new OracleListSqlHelper(this.dataSource, query);

    helper.getStandardFields().addFields(query.getListStructure());
    
    helper.addMapping("phone", "PHONE_NO");

    helper.setSimpleSqlQuery("PERSON");
    return helper.execute(MyModel.class);
  }
  ...
}

6.2.6. Quick Overview on How to Use

Here is a quick summary on how one can create and use a list, based on the material described in this chapter.

  1. Create a data query. This basically means a service layer method that takes at least a ListQuery for its argument, executes the database query, and returns ListItemsData. To execute the query, one must also specify the binding between model object fields and query result set fields.
  2. Create a list widget. The (bean) list widget is used to define the columns and its labels together with sorting and filtering information. Also, one must define a data provider — either memory-based or back-end — that invokes the data query created previously. Note that the data provider implementation provides the ListQuery object (containing information about the list, the constraints, and the rows expected) for the query, and expects a ListItemsData object as a result. Finally, the created list must be added by its creator widget simply like following: this.addWidget("myList", createdList);
  3. Describe the layout in JSP. Here you can use the list tags provided by Aranea. These are described below.

6.3. Selecting List Rows

This section shows how a user can choose list rows with a check box or a radio button. The solution described here is also integrated into Aranea lists, so it is quite easy to use. Firstly, Aranea provides a check box tag (<ui:listRowCheckBox/>) and a radio button tag (<ui:listRowRadioButton/>) for list rows. These are meant for the user to check multiple rows or just one row to submit them once the user clicks on a button. Usually, these tags don't require any attributes (unless one needs to customize style or javascript), and work only with the list where they are used, even if there are multiple lists on the page.

In addition, Aranea provides a tag that selects or unselects all row check boxes in the list. It is named <ui:listSelectAllCheckBox/>. Again, it requires zero configuration.

Now, these tags are useful because the next step is just getting the model objects from the list with the ListWidget.getSelectedRows() method. Or, if a radio button was used, and, therefore, only one selected row is expected, the ListWidget.getSelectedRow() method can be used. If no rows were selected then getSelectedRows() would return an empty list, and getSelectedRow() would return null.

There is also an advanced feature in case the list row check boxes are used. One can make the list remember the selected rows in case the user switches between the (list) pages. It can be achieved by calling list.setSelectFromMultiplePages(true) (by default it is false). Once enabled, the list and the tags use the equals() method of the list row data object to know whether the row must be checked or unchecked. Therefore, when the user goes back to the list page where some rows were selected before then they would still appear selected.

Note

Enabling the selectFromMultiplePages option, that makes list remember previously selected rows, requires caution because the data model objects needs to have the equals() to correctly compare them. Otherwise, the "same" object could appear several times in the returned selected rows list. This is the reason why it is turned off by default.

6.4. List JSP Tags

6.4.1. <ui:list>

Starts a list context. List view model, list sequence view model and list id are made accessible to inner tags as EL variables.

Attributes

AttributeRequiredDescription
idnoList widget id.
varSequencenoName of variable that represents list sequence info (by default listSequence).

Variables

VariableDescriptionType
listView model of list.ListWidget.ViewModel
listSequence (unless changed with varSequence attribute).View model of list sequence info.SequenceHelper.ViewModel
listIdId of list.String
listFullIdFull id of list.String

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
    ...
</ui:list>

6.4.2. <ui:listFilter>

Represents list filter. Introduces an implicit form (<ui:form>), so one can place form elements under it.

This tag has no attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  <ui:listFilter>
    <ui:row>
        <ui:cell>
            <ui:textInput id="field1Filter"/>
        </ui:cell>
    
        <ui:cell>
            <ui:textInput id="field2Filter"/>
        </ui:cell>

        ...

    </ui:row>
  </ui:listFilter>
</ui:list>

6.4.3. <ui:listFilterButton> and <ui:listFilterClearButton>

<ui:listFilterButton> renders list's filter form filtering activation button and registers a keyboard handler, so that pressing ENTER key in any filter form field activates list filtering. <ui:listFilterClearButton> renders list's filter form clearing button, pressing it sends server-side event that clears all active list filters.

Both of these tags must be used inside <ui:listFilter> tag.

Attributes

AttributeRequiredDescription
renderModenoPossible values are button, input—filter button is rendered with corresponding HTML tags, or empty in which case JSP author must provide suitable content for this tag by themself (with an image, for example). Default rendermode is button.
onClickPreconditionnoPrecondition for deciding whether registered onclick event should go server side or not. If left unspecified, this is considered to be true.
showLabelnoIndicates whether button label is shown.Value should be true or false, default is false—using true is pointless with these particular tags, it only has some effect when specified renderMode is empty and tags body is left empty too.
Also have all common form element rendering attributes plus standard style and styleClass attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
...
  <ui:listFilter>
    <ui:row>
      <!-- Bunch of filter fields in cells -->
      <ui:cell>
        <ui:listFilterButton/>
        <ui:listFilterClearButton/>
      </ui:cell>
    <ui:row>
  </ui:listFilter>

6.4.4. <ui:listRows>

Iterating tag that gives access to each row on the current list page. The row is by default accessible as EL variable row.

Attributes

AttributeRequiredDescription
varnoName of variable that represents individual row (by default "row").

Variables

VariableDescriptionType
row (unless changed with var attribute).Object held in current row.Object

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  <ui:listFilter>
    ...
  </ui:listFilter>
  
  <ui:listRows>
    <ui:row>
        <!-- In each row, object in this list row is accessible -->
        <ui:cell>
            <c:out value="${row.field1}"/>
        </ui:cell>

        <ui:cell>
            <c:out value="${row.field2}"/>
        </ui:cell>

        ...
    </ui:row>
  </ui:listRows>
</ui:list>

6.4.5. <ui:listRowButton>

Represents an HTML form button (not tied to any Control or FormElement). Default styleClass="aranea-button", rendered with HTML <button ...> tag.

Attributes

AttributeRequiredDescription
eventId no Event triggered when button is clicked.
id no Button id, allows to access button from JavaScript.
labelId no Id of button label.
onClickPrecondition no Precondition for deciding whether onclick event should go server side or not. If left unspecified this is set to return true;.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
Also has standard styleClass, updateRegions and globalUpdateRegions attributes.

6.4.6. <ui:listRowLinkButton>

Represents a HTML link with an onClick JavaScript event. Default styleClass="aranea-link-button", rendered with HTML <a href="javascript:" ...> tag.

Attributes

AttributeRequiredDescription
eventId no Event triggered when link is clicked.
id no Link id, allows to access link from JavaScript.
labelId no Id of link label.
onClickPrecondition no Precondition for deciding whether onclick event should go server side or not. If left unspecified this is set to return true;.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
Also has standard styleClass, updateRegions and globalUpdateRegions attributes.

Examples

<?xml version="1.0" encoding="UTF-8"?>
<ui:list id="list">
  ...  
  <ui:listRows>
    <ui:row>
      ...
      <ui:cell>
        <ui:listRowLinkButton eventId="edit">
          <img src="editButton.png"/>
        </ui:listRowLinkButton>
      </ui:cell>
      ...
    </ui:row>
  </ui:listRows>
</ui:list>

6.4.7. <ui:listRowCheckBox> And <ui:listRowRadioButton>

Represents a list row check box and a list row radio button that are bound to the list row. The <ui:listRowCheckBox> accompanies with the <ui:listSelectAllCheckBox> that lets the user mark all list row check boxes (in the same list) checked or unchecked.

Both <ui:listRowCheckBox> and <ui:listRowRadioButton> usually require no configuration. However, if one needs to change something, the tags provide similar attributes.

Attributes

AttributeRequiredDescription
valueno(Check box only!) Specifies a custom value (when it is submitted). Default value is selected.
labelIdnoSpecifies a custom label for the check box or the radio button.
disablednoSpecifies whether the input should be rendered as disabled. Default is active state.
onclicknoSpecifies custom onclick event. Default is none.
accessKeynoSpecifies custom acceskey (defined by HTML). Default is none.
checkednoSpecifies the initial state of the check box or radio button. Default is unchecked.
tabindexnoThis attribute specifies the position of the current element in the tabbing order for the current document. This value must be a number between 0 and 32767.
stylenoInline (CSS) style for HTML tag.
styleClassnoCSS class for the tag.
onChangeEventIdnoThe event handler name (in the parent widget of the list) that wants to know when a row selection changes. The parameter for the event handler is the rowRequestId.
eventPreconditionnoA JavaScript event precondition on whether the onchange event should go server-side.

See also Section 6.3, “Selecting List Rows”

6.5. Editable Lists

EditableListWidget and EditableBeanListWidget are ListWidgets wrapped around FormListWidget (See Section 5.3, “Form Lists” about it) which gathers data with the help from ListWidget.

Both editable list widgets have just one constructor and one additional getter (compared to ListWidget):

public EditableListWidget(FormRowHandler rowHandler);
public EditableBeanListWidget(FormRowHandler rowHandler, Class beanClass);

// gets the wrapped form list
public BeanFormListWidget getFormList();

Most important component of editable lists is FormListWidget's RowHandler, refer to Section 5.3, “Form Lists” about implementing that interface. Other than required implementation of RowHandler, editable lists do not differ from ListWidgets.

public class SampleEditableListWidget {
  private EditableBeanListWidget list;
  
  protected void init() throws Exception {
    setViewSelector("sampleEditableListView");
    
    list = new EditableBeanListWidget(buildFormRowHandler(), SomeBean.class);
    list.setDataProvider(buildListDataProvider());
    list.setOrderableByDefault(true);
    // list has only two columns of which only one is editable
    list.addField("immutable", "#ImmutableColumnLabel", false);
    list.addField("mutable", "#MutableColumnLabel").like();

    addWidget("sampleEditableList", list);
  }
  
  private FormRowHandler buildFormRowHandler() throws Exception {
    // return formRowHandler, see the form list example
  };
  
  private private ListDataProvider buildListDataProvider() throws Exception {
    // return data provider
  }
}

JSP view for this sample widget is presented below:

...
<ui:formList id="sampleEditableList">
  <!-- List filter definition, usual -->
  <!-- Editable lists body -->
  <ui:formListRows>
    <ui:row>
      <ui:cell>
        <!-- Row object is accessible as 'row' just as in lists -->
        <c:out value="${row.immutable}"/>
      </ui:cell>
      <ui:cell>
        <!-- But the implicit form tag for current row form is also present, so... -->
        <ui:formElement id="mutable">
          <ui:textInput/>
        </ui:formElement>
      </ui:cell>
    </ui:row>
  </ui:formListRows>
</ui:formList>
...

Full editable list example is bundled with Aranea examples.