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
org.araneaframework.uilib.list.dataprovider.BackendListDataProvider
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:
new BeanListWidget(MyModel.class)
creates a new list widget that is associated with the
JavaBean
model class
MyModel
.
myList.setOrderableByDefault(true)
configures the following fields as orderable.
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.
myList.setInitialOrder("name", true)
sets the list to be ordered by field "name" by default.
myList.setListDataProvider(new
MyListDataProvider())
sets the data provider for the list.
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
.
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.
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.
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.ListFilter
s and
FormElement
s. 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:
setIgnoreCase(boolean)
one assigns new filters to ignore case (default) or not. This applies to filters that use String comparison.
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 method | ListFilter class | Description |
---|---|---|
eq() | EqualFilter | Tests 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() | EqualFilter | Tests if the value of a certain list field is equal to a certain constant. This filter is always enabled. |
gt(), lt() | GreaterThanFilter, LowerThanFilter | Tests 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, LowerThanFilter | Tests if the value of a certain list field is greater than (lower than) a certain constant. This filter, if used, is always enabled. |
like() | LikeFilter | Tests 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() | LikeFilter | Tests if a certain constant pattern matches with the value of a certain list field. This filter is always enabled. |
startsWith(), endsWith() | LikeFilter | This 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() | LikeFilter | Tests 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() | NullFilter | Tests 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() | NullFilter | Tests if the value of a certain list field is null (is not null). This filter is always enabled. |
range() | RangeFilter | Tests 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() | RangeInRangeFilter | Tests 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() | InFilter | Tests if the value of a list field is among the values of a MultiSelectControl. It does a case-sensitive search for this. |
sqlFunction() | SqlFunctionFilter | Tests 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 FormElement
s 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:
Property | Default value | Customizing | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Id | Same as the list field Id. | Call
addField(...).<filter>("myCustomId"); | ||||||||||||||||||||
Label | Same as the label of the associated list field. | Call
addField(...).useCustomLabel("myCustomLabel").xxx(...); | ||||||||||||||||||||
Control | Is selected considering the type of the associated list
field:
| Call addField(...).xxx(new
MyCustomControl()); | ||||||||||||||||||||
Data | Corresponds to the type of the associated list field. | Call
addField(...).useFieldType(MyType.class).xxx(...); | ||||||||||||||||||||
Initial value | Always 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 . | ||||||||||||||||||||
Mandatory | Always 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 . | ||||||||||||||||||||
FormElement | See 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.
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:
ListWidget
is defined.)
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.
Map
and OrderInfo
.
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:
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:
ListSqlHelper helper = new
OracleListSqlHelper(this.dataSource, query)
constructs
OracleListSqlHelper
- an Oracle specific
ListSqlHelper
- and passes it the
DataSource
and
ListQuery
data.
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.
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.
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:
ListSqlHelper helper = new
OracleListSqlHelper(this.dataSource, query)
constructs
OracleListSqlHelper
- an Oracle specific
ListSqlHelper
- and passes it the
DataSource
and
ListQuery
data.
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.
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.
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.
helper.getDatabaseFilterParams()
returns SQL parameters of
WHERE
clause or empty list if there are none.
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.
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.
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
:
SELECT
clause.
Some of them can be used for filtering and ordering as well.
Field name can be e.g "birthday" or "group.name".
Converter
object to ListSqlHelper
.
E.g. booleans have to be converted into numeric (0 or 1) values.
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:
Method | Purpose |
---|---|
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:
Method | Purpose |
---|---|
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. |
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.
Method | Purpose |
---|---|
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);
}
...
}
Here is a quick summary on how one can create and use a list, based on the material described in this chapter.
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.
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);
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.
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.
Starts a list context. List view model, list sequence view model and list id are made accessible to inner tags as EL variables.
Attribute | Required | Description |
---|---|---|
id | no | List widget id. |
varSequence | no | Name of variable that represents list sequence info (by default listSequence). |
Variable | Description | Type |
---|---|---|
list | View model of list. | ListWidget.ViewModel |
listSequence (unless changed with varSequence attribute). | View model of list sequence info. | SequenceHelper.ViewModel |
listId | Id of list. | String |
listFullId | Full id of list. | String |
Represents list filter. Introduces an implicit form (<ui:form>), so one can place form elements under it.
This tag has no attributes.
<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.
Attribute | Required | Description |
---|---|---|
renderMode | no | Possible 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 . |
onClickPrecondition | no | Precondition for deciding whether registered onclick
event should go server side or not. If left unspecified, this
is considered to be true . |
showLabel | no | Indicates 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. |
style
and styleClass
attributes.Iterating tag that gives access to each row on the current list page. The row is by default accessible as EL variable row.
Attribute | Required | Description |
---|---|---|
var | no | Name of variable that represents individual row (by default "row"). |
Variable | Description | Type |
---|---|---|
row (unless changed with var attribute). | Object held in current row. | Object |
<?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>
Represents an HTML form button (not tied to any
Control
or FormElement
). Default
styleClass="aranea-button"
, rendered with HTML
<button ...>
tag.
Attribute | Required | Description |
---|---|---|
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; . |
tabindex | no | This 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. |
styleClass
,
updateRegions
and
globalUpdateRegions
attributes.Represents a HTML link with an onClick
JavaScript event. Default
styleClass="aranea-link-button"
, rendered with HTML
<a href="javascript:" ...>
tag.
Attribute | Required | Description |
---|---|---|
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; . |
tabindex | no | This 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. |
styleClass
,
updateRegions
and
globalUpdateRegions
attributes.
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.
Attribute | Required | Description |
---|---|---|
value | no | (Check box only!) Specifies a custom value (when it is submitted).
Default value is selected . |
labelId | no | Specifies a custom label for the check box or the radio button. |
disabled | no | Specifies whether the input should be rendered as disabled. Default is active state. |
onclick | no | Specifies custom onclick event. Default is none. |
accessKey | no | Specifies custom acceskey (defined by HTML). Default is none. |
checked | no | Specifies the initial state of the check box or radio button. Default is unchecked. |
tabindex | no | This 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. |
style | no | Inline (CSS) style for HTML tag. |
styleClass | no | CSS class for the tag. |
onChangeEventId | no | The 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 . |
eventPrecondition | no | A JavaScript event precondition on whether the onchange event should go server-side. |
See also Section 6.3, “Selecting List Rows”
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 ListWidget
s.
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.