JSF value binding
Today i will explain three types of value binding:
- “direct” value binding (one setter / getter for each value)
- “list based” value binding (one setter / getter for a list of values)
- “map based” value binding (one setter / getter for a map of values)
Direct value binding
This is the classic usage of value binding in jsf, the following facelets-snippet is self-explanatory:
<h:inputText value="#{bean.foo}"/>
<h:inputText value="#{bean.bar}"/>
Our bean have a setter and a getter for foo and bar:
class Bean {
private String foo, bar;
public String getFoo() ...
public void setFoo(String) ...
public String getBar() ...
public void setBar(String) ...
}
List based value binding
I use this to avoid setter and getter for similar values/properties, i.e.
<h:inputText value="#{bean.product[0]}"/>
<h:inputText value="#{bean.product[1]}"/>
<h:inputText value="#{bean.product[2]}"/>
...
With the “classic” value binding i have to write a lot of getter/setter (product0, product1, product2,…). But with list based value binding its much easier.
class Bean {
private static final int MAX_NUMBER_OF_PRODUCTS = 5;
private final Product[] products = new Product[MAX_NUMBER_OF_PRODUCTS];
public Bean () {
for (int i = 0; i < MAX_NUMBER_OF_PRODUCTS; i++) {
products[i] = null;
}
}
public List<Product> getProduct() {
return Arrays.asList(Product);
}
}
The initialization of the internal array/list is necessary for correct working! During runtime jsf will access each value via index (0, 1, 2, …), a uninitialized list will throw a IndexOutOfBounds Exception.
Map based value binding
I used this type of value binding in a generic “CRUD list” view to implement filtering in a rich:dataTable. I want a selectbox for each “filterable” column:
<rich:column>
<f:facet name="header">
<h:selectOneMenu value="#{bean.filterValue['foo']}">
<f:selectItems value="#{bean.filterValues['foo']}" />
</h:selectOneMenu>
</f:facet>
...
</rich:column>
<rich:column>
<f:facet name="header">
<h:selectOneMenu value="#{bean.filterValue['bar']}">
<f:selectItems value="#{bean.filterValues['bar']}" />
</h:selectOneMenu>
</f:facet>
...
</rich:column>
To get this to work we need a “customized” map implementation:
class ArrayMap<K, T> extends HashMap<K, T> {
private static final long serialVersionUID = -5766406097936988242L;
@Override
public T get(final Object theKey) {
if (!containsKey(theKey)) {
// to avoid typos in the ui we throw a exception if a "unknown" value-name is used
throw new IllegalArgumentException(String.format("The given value-name '%s' is not available", theKey));
}
return super.get(theKey);
}
public void set(final K key, final T value) {
super.put(key, value);
}
}
This ArrayMap have a getter and a setter (the “normal” Map have only get and put), for jsf value binding we need a set and a get.
Here is the final filter – bean:
class FilterBean {
/**
* Contain all available filter-values for each supported filter-name.
*/
private final Map<String, List<SelectItem>> filterValues = new ArrayMap<String, List<SelectItem>>();
/**
* Contain the current filter-value for each supported filter-name.
*/
private final ArrayMap<String, Object> filterValue = new ArrayMap<String, Object>();
/*
* Constructor.
*/
public FilterBean() {
initFilterValue();
}
/**
* @return a map of lists with {@link SelectItem}s
*/
public Map<String, List<SelectItem>> getFilterValues() {
return filterValues;
}
/**
* @return the map of filter values
*/
public ArrayMap<String, Object> getFilterValue() {
return filterValue;
}
/**
* Prepare <tt>filterValue</tt>, add all supported filter-names to the list.
*/
private void initFilterValue() {
filterValue.set("foo", null);
filterValue.set("bar", null);
}
/**
* Prepare <tt>filterValues</tt>, the given list contains all rows for the datatable. Each filter-selectbox must
* show a unique list of available filter-values (per supported filter column).
*/
public synchronized void initFilterValues(final List<T> theInitialFilterList) {
filterValues.clear();
filterValues.put("foo", getFilterSelectItemsFoo(theInitialFilterList));
filterValues.put("bar", getFilterSelectItemsBar(theInitialFilterList));}
}
}
The method initFilterValues(final List<T>) will be triggered from a external bean. Then the filter-bean can create a list of SelectItems for each filter-column.
If the User select a value from such a filter-list the value binding will call our ArrayMap.set method.
Try it