Using custom converter for h:selectOneMenu

In this tutorial we will show you how to convert a select-item of a selectOneMenu or a selectOneListBox into a key and back into the representing object. This is a common issue regarding JSF converters and the h:selectOneMenu and h:selectOneListBox components. The purpose of this tutorial is to display many select-items in a dropdown and convert the key back into the representation of that same object. Since JSF doesn’t have any default converters which we can use we must create our own custom converter.

Maven Dependencies

Here are the dependencies required for this project. You can add this via maven or make sure every library is added to your classpath.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.memorynotfound.jsf.converter</groupId>
    <artifactId>custom-converter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>JSF - ${project.artifactId}</name>
    <url>http://memorynotfound.com</url>
    <packaging>war</packaging>

    <dependencies>
        <!-- java ee api -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- JSF api and impl -->
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.2.12</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.2.12</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>

A Simple POJO

Let’s start by describing our simple POJO. We have a Beer bean which has an id and a name.

package com.memorynotfound.jsf;

public class Beer {

    private Integer id;
    private String brand;

    public Beer(Integer id, String brand) {
        this.id = id;
        this.brand = brand;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}

Managed Bean

Next we have our managed bean which has a list of beer objects which we want to display on the page.

package com.memorynotfound.jsf;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.SessionScoped;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@ManagedBean
@SessionScoped
public class BeersBean implements Serializable {

    private Beer selectedBeer;
    private List<Beer> beers;

    public BeersBean(){
        beers = new ArrayList<Beer>();
        beers.add(new Beer(10, "La Chouffe"));
        beers.add(new Beer(20, "Stella Artois"));
        beers.add(new Beer(30, "Westmalle Trippel"));
    }

    public Beer getSelectedBeer() {
        return selectedBeer;
    }

    public void setSelectedBeer(Beer selectedBeer) {
        this.selectedBeer = selectedBeer;
    }

    public List<Beer> getBeers() {
        return beers;
    }

    public void setBeers(List<Beer> beers) {
        this.beers = beers;
    }

    public Beer getBeer(Integer id) {
        if (id == null){
            throw new IllegalArgumentException("no id provided");
        }
        for (Beer beer : beers){
            if (id.equals(beer.getId())){
                return beer;
            }
        }
        return null;
    }
}

Display select-items using selectOneMenu

We can render our collection of beer objects using the h:selectOneMenu component. The list will render ok. However, the problem occurs when we choose any beer and we try to populate the selectedBeer property with it. We’ll receive a conversion error. The solution is to add a custom converter which will translate the key into the representing object, as shown next.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
    <title>Custom converter for h:selectOneMenu</title>
</h:head>
<h:body>

    <h2>JSF 2 custom converter for h:selectOneMenu example</h2>
    <h:form>
        <h:panelGrid columns="2">
            <h:outputLabel value="Favorite beer:"/>

            <h:selectOneMenu value="#{beersBean.selectedBeer}" converter="beerConverter">
                <f:selectItems value="#{beersBean.beers}" var="beer"
                               itemValue="#{beer}"
                               itemLabel="#{beer.brand}"/>
            </h:selectOneMenu>

            <h:commandButton value="submit" action="result"/>
        </h:panelGrid>
    </h:form>

</h:body>
</html>

Custom Converter

The solution is to write a custom converter. The getAsString method we extract and return the id of the beer, and in the getAsObject method, the submitted beer id is converted back into a Beer instance.

package com.memorynotfound.jsf;

import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;

@FacesConverter(value = "beerConverter")
public class BeerConverter implements Converter {

    @Override
    public Object getAsObject(FacesContext ctx, UIComponent uiComponent, String beerId) {
        ValueExpression vex =
                ctx.getApplication().getExpressionFactory()
                        .createValueExpression(ctx.getELContext(),
                                "#{beersBean}", BeersBean.class);

        BeersBean beers = (BeersBean)vex.getValue(ctx.getELContext());
        return beers.getBeer(Integer.valueOf(beerId));
    }

    @Override
    public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object beer) {
        return ((Beer)beer).getId().toString();
    }

}

Displaying the result

This code will display the submitted values. We display the selected beer.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
    <title>Custom converter for h:selectOneMenu</title>
</h:head>
<h:body>
    <h2>JSF 2 custom converter for h:selectOneMenu example</h2>
    <p>Favorite beer: #{beersBean.selectedBeer.id}, #{beersBean.selectedBeer.brand}</p>
</h:body>
</html>

Demo

URL: localhost:8080/jsf-custom-converter

jsf custom converterjsf custom converter

References

Download

You may also like...