Ho problemi a capire come usare la selezione in JSF 2 con POJO / entity framework in modo efficace. Ad esempio, sto tentando di selezionare un’ quadro Warehouse
tramite il menu a discesa sottostante:
E il bean gestito sotto:
@Named @ViewScoped public class Bean { private Warehouse selectedWarehouse; private List availableWarehouses; // ... @PostConstruct public void init() { // ... availableWarehouses = new ArrayList(); for (Warehouse warehouse : warehouseService.listAll()) { availableWarehouses.add(new SelectItem(warehouse, warehouse.getName())); } } // ... }
Si noti che utilizzo l’intera quadro Warehouse
come valore di SelectItem
.
Quando invio il modulo, questo non riesce con il seguente messaggio di facce:
Errore conversione valore di impostazione ‘[email protected]’ per ‘Convertitore nullo’.
Speravo che JSF potesse semplicemente impostare l’object Warehouse
corretto sul mio bean gestito quando lo ho SelectItem
in un object SelectItem
. Avvolgere la mia entity framework all’interno di SelectItem
intendeva saltare la creazione di un Converter
per la mia quadro.
Devo davvero usare un Converter
ogni volta che voglio fare uso di quadro nel mio ? Dovrebbe essere ansible per JSF estrarre semplicemente l’elemento selezionato dall’elenco degli articoli disponibili. Se devo davvero usare un convertitore, qual è il modo pratico di farlo? Finora mi sono avvicinato a questo:
Converter
per l’entity framework. getAsString()
. Penso di non averne bisogno poiché la proprietà label di SelectItem
verrà utilizzata per visualizzare l’etichetta dell’opzione dropdown. getAsObject()
. Penso che questo sarà usato per restituire il SelectItem
corretto o entity framework in base al tipo di campo selezionato definito nel bean gestito. getAsObject()
mi confonde. Qual è il modo efficace per farlo? Avendo il valore di stringa, come ottengo l’object entity framework associata? Devo interrogare l’object quadro dall’object servizio in base al valore stringa e restituire l’entity framework? O forse in qualche modo posso accedere all’elenco delle quadro che formano gli elementi di selezione, eseguirne il ciclo per trovare l’ quadro corretta e restituire l’entity framework?
Qual è l’approccio normale di questo?
JSF genera HTML. HTML è in termini Java fondamentalmente una String
grande. Per rappresentare oggetti Java in HTML, devono essere convertiti in String
. Inoltre, quando viene inviato un modulo HTML, i valori inviati vengono trattati come String
nei parametri di richiesta HTTP. Sotto le copertine, JSF li estrae da HttpServletRequest#getParameter()
che restituisce String
.
Per convertire tra un object Java non standard (ovvero non una String
, Number
o Boolean
per cui EL ha conversioni incorporate, o Date
per cui JSF fornisce il
incorporato
), devi fornire un Converter
personalizzato. Il SelectItem
non ha uno scopo speciale. È solo un avanzo da JSF 1.x quando non è stato ansible fornire ad es. List
direttamente a
. Non ha inoltre alcun trattamento speciale per quanto riguarda le etichette e la conversione.
È necessario implementare il metodo getAsString()
in modo tale che l’object Java desiderato sia stato rappresentato in una rappresentazione String
univoca che può essere utilizzata come parametro di richiesta HTTP. Normalmente, l’ID tecnico (la chiave primaria del database) è usato qui.
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")); } }
Si noti che la restituzione di una stringa vuota in caso di valore del modello null / vuoto è significativa e richiesta da javadoc . Vedi anche Utilizzo di “Please select” f: selectItem con valore null / vuoto all’interno di ap: selectOneMenu .
È necessario implementare getAsObject()
in modo tale che esattamente la rappresentazione di String
restituita da getAsString()
possa essere riconvertita esattamente nello stesso object Java specificato come modelValue
in 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); } }
In altre parole, è necessario essere tecnicamente in grado di restituire l’object restituito come argomento modelValue
di getAsString()
e quindi ritrasferire la stringa ottenuta come argomento getAsObject()
di getAsObject()
in un ciclo infinito.
Infine basta annotare il Converter
con @FacesConverter
per agganciare il tipo di object in questione, quindi JSF si occuperà automaticamente della conversione quando il tipo di Warehouse
viene inserito nell’immagine:
@FacesConverter(forClass=Warehouse.class)
Questo era l’approccio “canonico” del JSF. Dopo tutto non è molto efficace in quanto potrebbe anche aver appena afferrato l’object da
. Ma il punto più importante di un Converter
è che restituisce una rappresentazione String
univoca , in modo che l’object Java possa essere identificato da una semplice String
adatta per il passaggio in HTTP e HTML.
Libreria delle utility JSF OmniFaces ha un SelectItemsConverter
che funziona in base al risultato toString()
dell’ quadro. In questo modo non dovrai più getAsObject()
di getAsObject()
e di costose operazioni di business / database. Per alcuni esempi di uso concreto, vedere anche la vetrina .
Per usarlo, basta registrarlo come di seguito:
E assicurati che toString()
della tua quadro Warehouse
restituisca una rappresentazione univoca dell’ quadro. Ad esempio, è ansible restituire direttamente l’ID:
@Override public String toString() { return String.valueOf(id); }
O qualcosa di più leggibile / riutilizzabile:
@Override public String toString() { return "Warehouse[id=" + id + "]"; }
Non correlato al problema, dal momento che JSF 2.0 non richiede più esplicitamente un List
come valore
. Basterebbe anche solo un List
.
private Warehouse selectedWarehouse; private List availableWarehouses;
Esempio di convertitore generico JSF con ABaseEntity e identificatore:
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 selectItems = null; for (UIComponent uic : comp.getChildren()) { if (uic instanceof UISelectItems) { Long itemId = Long.valueOf(value); selectItems = (List ) ((UISelectItems) uic).getValue(); if (itemId != null && selectItems != null && !selectItems.isEmpty()) { Predicate predicate = i -> i.getIdentifier().equals(itemId); item = selectItems.stream().filter(predicate).findFirst().orElse(null); } } } return item; } }
E l’uso: