Ontem recebi a seguinte mensagem de um leitor da revista Java Magazine, chamado Marvin:
Acompanhei, recentemente, sua série sobre Java EE 6 na prática. Gostaria de parabenizá-lo, pelo seu material, pois é muito bom e está me ajudando bastante. Contudo, estou enfrentando um problema e gostaria, se possível, de uma ajuda sua.
Vamos supor que eu tenha uma classe de entidades Venda e uma classe de entidade Cliente. Na classe Venda eu tenho um atributo do tipo Cliente, com uma associação ManyToOne. Estou tentando implementar uma tela de cadastro de venda onde será disponibilizado um combo-box com a lista de clientes que o usuário poderá escolher. Como deve implementar esse combo-box no JSF? Consegui implementar um combo-box que traz a lista de um enum, mas com um entity não estou conseguindo.
Você poderia indicar o caminho das pedras?
Olá Marvin,
Tenho aqui um exemplo que pode te servir. Num sistema que desenvolvo, existe uma classe Institution (o sistema está todo em inglês) que é associada many-to-one com InstitutionType. No formulário de cadastro de instituições tenho:
[cce_java]
<h:selectOneMenu id=”type” value=”#{manageInstitutionsAction.selectedEntity.type}” converter=”#{coreController.institutionTypeConverter}” required=”true”>
<f:selectItem itemLabel=”#{msgsCore[‘manageInstitutions.form.type.blankOption’]}” itemValue=”#{null}” />
<f:selectItems value=”#{coreController.institutionTypes}” />
</h:selectOneMenu>
[/cce_java]
De baixo para cima, vamos começar pelo [cci_java]
A linha anterior, [cci_java]
O campo se refere a um valor por meio do parâmetro [cci_java]value=”#{manageInstitutionsAction.selectedEntity.type}”[/cci_java]. O bean [cci_java]manageInstitutionsAction[/cci_java] possui um método [cci_java]getSelectedEntity()[/cci_java] que retorna uma instância de [cci_java]Institution[/cci_java]. Esta classe possui um atributo [cci_java]type[/cci_java], do tipo [cci_java]InstitutionType[/cci_java]. Assim, quando enviarmos o formulário este atributo deste objeto será preenchido com o objeto escolhido na lista. O JSF também escolhe automaticamente o elemento correto da lista quando estivermos editando uma instituição e o objeto retornado por [cci_java]getSelectedEntity()[/cci_java] já tiver um valor preenchido do atributo [cci_java]type[/cci_java].
Mas em HTML tudo é representado por Strings, então o JSF tem que saber como converter objetos para String e vice-versa. Para os tipos mais conhecidos (inteiros, números reais, datas) o JSF já sabe, mas para um objeto [cci_java]InstitutionType[/cci_java] não. O segredo está no parâmetro [cci_java]converter=”#{coreController.institutionTypeConverter}”[/cci_java]. Ele associa ao campo um conversor JSF. O conversor associado é o que for retornado pelo seguinte método:
[cce_java]
public InstitutionTypeConverterFromId getInstitutionTypeConverter() {
if (institutionTypeConverter == null) institutionTypeConverter = new InstitutionTypeConverterFromId(institutionTypeDAO);
return institutionTypeConverter;
}
[/cce_java]
E a classe [cci_java]InstitutionTypeConverterFromId[/cci_java] é assim:
[cce_java]
package br.com.engenhodesoftware.sigme.core.controller.converters;
import br.com.engenhodesoftware.sigme.core.domain.InstitutionType;
import br.com.engenhodesoftware.sigme.core.persistence.InstitutionTypeDAO;
import br.com.engenhodesoftware.util.ejb3.controller.EntityJSFConverterFromId;
import br.com.engenhodesoftware.util.ejb3.persistence.BaseDAO;
public class InstitutionTypeConverterFromId extends EntityJSFConverterFromId<InstitutionType> {
private static final long serialVersionUID = 1L;
private InstitutionTypeDAO institutionTypeDAO;
public InstitutionTypeConverterFromId(InstitutionTypeDAO institutionTypeDAO) {
this.institutionTypeDAO = institutionTypeDAO;
}
@Override
protected Class<InstitutionType> getDomainClass() {
return InstitutionType.class;
}
@Override
protected BaseDAO<InstitutionType> getDAO() {
return institutionTypeDAO;
}
}
[/cce_java]
Ela simplesmente provê a classe de domínio e o DAO desta classe de domínio para os métodos implementados na sua superclasse, que é uma generalização de conversão de qualquer entidade para string e vice-versa, utilizando o atributo [cci_java]id[/cci_java] (identificador de persistência, “chave-primária”) do objeto. E abaixo segue o código-fonte dela.
[cce_java]
package br.com.engenhodesoftware.util.ejb3.controller;
import br.com.engenhodesoftware.util.ejb3.persistence.BaseDAO;
import br.com.engenhodesoftware.util.ejb3.persistence.PersistentObject;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
public abstract class EntityJSFConverterFromId<T extends PersistentObject> implements Converter, Serializable {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(EntityJSFConverterFromId.class.getCanonicalName());
protected abstract Class<T> getDomainClass();
protected abstract BaseDAO<T> getDAO();
@Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
T entity = null;
logger.log(Level.INFO, “Trying to convert to an instance of {0} from the id: {1}”, new Object[] {getDomainClass().getSimpleName(), value});
// Checks for nulls.
if (value != null) {
// Loads the entity given the id.
try {
Long id = Long.valueOf(value);
entity = getDAO().retrieveById(id);
}
catch (NumberFormatException e) {
logger.log(Level.WARNING, “Value is not a number (Long): {0}”, value);
return null;
}
}
logger.log(Level.INFO, “Returning: {0}”, entity);
return entity;
}
@Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
// Checks that the supplied value is an entity of the class referred to by the converter.
if ((value != null) && (value.getClass().equals(getDomainClass()))) {
T entity = (T)value;
// Checks for null id and returns the id converted to string.
if (entity.getId() != null) return entity.getId().toString();
}
// If it doesn’t pass one of the previous checks, return an empty string.
return “”;
}
}
[/cce_java]
Como dita a especificação JSF, um conversor deve implementar os métodos [cci_java]getAsObject()[/cci_java] e [cci_java]getAsString()[/cci_java]. O primeiro recebe a representação String do objeto (no nosso caso, o ID) e retorna o objeto em si (no nosso caso, converte o ID para [cci_java]Long[/cci_java] e solicita ao DAO que recupere o objeto do banco de dados). O segundo faz o caminho oposto, de objeto para string (no nosso caso, basta obter o ID do objeto e convertê-lo para String).
O código usa tipos genéricos e pode parecer um pouco complicado, mas acredito que seja possível entendê-lo e, se preferir não usar genéricos, implementar diretamente o conversor para o seu tipo (classe Cliente), bastando implementar os métodos [cci_java]getAsObject()[/cci_java] e [cci_java]getAsString()[/cci_java] recuperando os objetos diretamente com seu [cci_java]ClienteDAO[/cci_java].
Bons estudos!
2 thoughts on “Mostrando objetos em um campo JSF tipo selectOne”
Olá Vitor,
Estava procurando uma maneira de inserir a String “Selecione” como default no meu combobox e graças a seu post consegui, pois as tentativas anteriores de outros posts não funcionaram.
Abraços
Fico contente, assim vejo que foi boa a ideia de responder às dúvidas que me mandam com um post no blog, pois podem acabar ajudando outras pessoas.
– Vítor