J'ai des problèmes pour comprendre comment utiliser efficacement la sélection dans JSF 2 avec POJO/entité. Par exemple, j'essaie de sélectionner une entité Warehouse
via la liste déroulante ci-dessous:
<h:selectOneMenu value="#{bean.selectedWarehouse}">
<f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
<f:selectItems value="#{bean.availableWarehouses}" />
</h:selectOneMenu>
Et le bean géré ci-dessous:
@Named
@ViewScoped
public class Bean {
private Warehouse selectedWarehouse;
private List<SelectItem> availableWarehouses;
// ...
@PostConstruct
public void init() {
// ...
availableWarehouses = new ArrayList<>();
for (Warehouse warehouse : warehouseService.listAll()) {
availableWarehouses.add(new SelectItem(warehouse, warehouse.getName()));
}
}
// ...
}
Notez que j'utilise l'entité Warehouse
entière comme valeur de SelectItem
.
Lorsque je soumets le formulaire, cela échoue avec le message de visages suivant:
Erreur de conversion en définissant la valeur "com.example.Warehouse@cafebabe" pour "Convertisseur nul".
J'espérais que JSF pourrait simplement définir l'objet Warehouse
correct sur mon bean géré lorsque je l'enveloppe dans un SelectItem
. Envelopper mon entité dans le SelectItem
était censé ignorer la création d'un Converter
pour mon entité.
Dois-je vraiment utiliser un Converter
chaque fois que je veux utiliser des entités dans mon <h:selectOneMenu>
? Il devrait être possible pour JSF d'extraire simplement l'élément sélectionné de la liste des éléments disponibles. Si je dois vraiment utiliser un convertisseur, quelle est la manière pratique de le faire? Jusqu'à présent, je suis arrivé à ceci:
Converter
pour l'entité.getAsString()
. Je pense que je n'en ai pas besoin car la propriété label du SelectItem
sera utilisée pour afficher le libellé de l'option déroulante.getAsObject()
. Je pense que cela sera utilisé pour retourner la bonne SelectItem
ou l'entité en fonction du type du champ sélectionné défini dans le bean géré.La getAsObject()
me confond. Quelle est la manière efficace de procéder? Ayant la valeur de chaîne, comment puis-je obtenir l'objet entité associé? Dois-je interroger l'objet entité à partir de l'objet service en fonction de la valeur de chaîne et renvoyer l'entité? Ou peut-être que je peux accéder à la liste des entités qui forment les éléments de sélection, les boucler pour trouver l'entité correcte et renvoyer l'entité?
Quelle est l'approche normale de cela?
JSF génère du HTML. Le HTML est en Java termes essentiellement un grand String
. Pour représenter Java objets en HTML, ils doivent être convertis en String
. De plus, lorsqu'un formulaire HTML est soumis, les valeurs soumises sont traitées comme String
dans les paramètres de requête HTTP. Sous les couvertures, JSF les extrait du HttpServletRequest#getParameter()
qui renvoie String
.
Pour effectuer la conversion entre un objet Java non standard (c'est-à-dire pas un String
, Number
ou Boolean
pour lequel EL a intégré des conversions, ou Date
pour laquelle JSF fournit une balise intégrée <f:convertDateTime>
), vous devez vraiment fournir un --- Converter
personnalisé. Le SelectItem
n'a aucun but particulier. C'est juste un reste de JSF 1.x quand il n'était pas possible de fournir par exemple List<Warehouse>
Directement vers <f:selectItems>
. Il n'a pas non plus de traitement spécial quant aux étiquettes et à la conversion.
Vous devez implémenter la méthode getAsString()
de telle manière que l'objet Java souhaité soit représenté dans un unique String
représentation qui peut être utilisée comme paramètre de requête HTTP. Normalement, l'ID technique (la clé primaire de la base de données) est utilisé ici.
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
if (modelValue == null) {
return "";
}
if (modelValue instanceof Warehouse) {
return String.valueOf(((Warehouse) modelValue).getId());
} else {
throw new ConverterException(new FacesMessage(modelValue + " is not a valid Warehouse"));
}
}
Notez que le retour d'une chaîne vide en cas de valeur de modèle nulle/vide est significatif et requis par javadoc . Voir aussi tilisation d'un "Veuillez sélectionner" f: selectItem avec une valeur nulle/vide dans un p: selectOneMen .
Vous devez implémenter getAsObject()
de telle sorte que exactement cette représentation String
telle que renvoyée par getAsString()
peut être reconverti en exactement le même objet Java spécifié comme modelValue
dans getAsString()
.
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
if (submittedValue == null || submittedValue.isEmpty()) {
return null;
}
try {
return warehouseService.find(Long.valueOf(submittedValue));
} catch (NumberFormatException e) {
throw new ConverterException(new FacesMessage(submittedValue + " is not a valid Warehouse ID"), e);
}
}
En d'autres termes, vous devez être techniquement en mesure de renvoyer l'objet renvoyé en tant qu'argument modelValue
de getAsString()
, puis de renvoyer la chaîne obtenue en tant qu'argument submittedValue
de getAsObject()
dans une boucle infinie.
Enfin juste annoter le Converter
avec @FacesConverter
pour accrocher le type d'objet en question, JSF se chargera alors automatiquement de la conversion lorsque Warehouse
le type entre dans l'image:
@FacesConverter(forClass=Warehouse.class)
C'était l'approche JSF "canonique". Ce n'est après tout pas très efficace car il aurait pu aussi juste récupérer l'objet du <f:selectItems>
. Mais le point le plus important d'une Converter
est qu'elle renvoie une représentation unique String
, de sorte que la Java l'objet pourrait être identifié par un simple String
adapté pour passer autour de HTTP et HTML.
La bibliothèque d'utilitaires JSF OmniFaces a un SelectItemsConverter
qui fonctionne en fonction du résultat toString()
de l'entité. De cette façon, vous n'avez plus besoin de manipuler getAsObject()
et les opérations commerciales/de base de données coûteuses. Pour quelques exemples d'utilisation concrète, voir aussi showcase .
Pour l'utiliser, enregistrez-le comme ci-dessous:
<h:selectOneMenu ... converter="omnifaces.SelectItemsConverter">
Et assurez-vous que la toString()
de votre entité Warehouse
renvoie une représentation unique de l'entité. Vous pouvez par exemple renvoyer directement l'ID:
@Override
public String toString() {
return String.valueOf(id);
}
Ou quelque chose de plus lisible/réutilisable:
@Override
public String toString() {
return "Warehouse[id=" + id + "]";
}
Unrelated au problème, car depuis JSF 2.0, il n'est plus explicitement requis d'avoir une valeur List<SelectItem>
Comme valeur <f:selectItem>
. Un simple List<Warehouse>
Suffirait également.
<h:selectOneMenu value="#{bean.selectedWarehouse}">
<f:selectItem itemLabel="Choose one .." itemValue="#{null}" />
<f:selectItems value="#{bean.availableWarehouses}" var="warehouse"
itemLabel="#{warehouse.name}" itemValue="#{warehouse}" />
</h:selectOneMenu>
private Warehouse selectedWarehouse;
private List<Warehouse> availableWarehouses;
Exemple de convertisseur générique JSF avec ABaseEntity et identifiant:
ABaseEntity.Java
public abstract class ABaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
public abstract Long getIdentifier();
}
SelectItemToEntityConverter.Java
@FacesConverter(value = "SelectItemToEntityConverter")
public class SelectItemToEntityConverter implements Converter {
@Override
public Object getAsObject(FacesContext ctx, UIComponent comp, String value) {
Object o = null;
if (!(value == null || value.isEmpty())) {
o = this.getSelectedItemAsEntity(comp, value);
}
return o;
}
@Override
public String getAsString(FacesContext ctx, UIComponent comp, Object value) {
String s = "";
if (value != null) {
s = ((ABaseEntity) value).getIdentifier().toString();
}
return s;
}
private ABaseEntity getSelectedItemAsEntity(UIComponent comp, String value) {
ABaseEntity item = null;
List<ABaseEntity> selectItems = null;
for (UIComponent uic : comp.getChildren()) {
if (uic instanceof UISelectItems) {
Long itemId = Long.valueOf(value);
selectItems = (List<ABaseEntity>) ((UISelectItems) uic).getValue();
if (itemId != null && selectItems != null && !selectItems.isEmpty()) {
Predicate<ABaseEntity> predicate = i -> i.getIdentifier().equals(itemId);
item = selectItems.stream().filter(predicate).findFirst().orElse(null);
}
}
}
return item;
}
}
Et utilisation:
<p:selectOneMenu id="somItems" value="#{exampleBean.selectedItem}" converter="SelectItemToEntityConverter">
<f:selectItem itemLabel="< select item >" itemValue="#{null}"/>
<f:selectItems value="#{exampleBean.availableItems}" var="item" itemLabel="${item.identifier}" itemValue="#{item}"/>
</p:selectOneMenu>