Form dinamiche con PrimeFaces
Nell’ ho cercato di spiegare come ottenere delle datatable dinamiche con e la Reflection. In questo nuovo articolo utilizzeremo la stessa tecnica per creare delle form di dettaglio dinamiche. Per fare ciò utilizzeremo il componente DynaForm del progetto PrimeFaces Extensions. Come potete vedere nei vari esempi presenti nello showcase, la DynaForm
è utile quando abbiamo una form descritta dinamicamente come ad esempio da database o da un file di configurazione. Cerchermo con lo stesso codice di gestire due POJO differenti: User e Car.
public class User implements Serializable{ private static final long serialVersionUID = 1L; private static Listusers = new ArrayList (); private Integer id; private String lastName; private String firstName; static{ users.add(new User(0, "Solid","Snake")); users.add(new User(1, "Vulcan","Raven")); users.add(new User(2, "Meryl","Silverburgh")); users.add(new User(3, "Hal","Emmerich")); users.add(new User(4, "Frank","Jaeger")); } public User(Integer id, String firstName, String lastName) { super(); this.id = id; this.lastName = lastName; this.firstName = firstName; } public User() {} public static List getAll(){ return users; } public static User get(final Integer id){ return (User) CollectionUtils.find(users, new Predicate() { public boolean evaluate(Object object) { return ((User) object).getId().equals(id); } }); } public static User store(User p){ if(p.getId() == null){ User maxUserId = Collections.max(users, new Comparator () { public int compare(User o1, User o2) { return o1.getId().compareTo(o2.getId()); } }); p.setId(maxUserId.getId()+1); users.add(p); }else{ users.set(p.getId(), p); } return p; } public static void delete(User p){ users.remove(p); } //getter e setters }
public class Car implements Serializable{ private static final long serialVersionUID = 1L; private static Listcars = new ArrayList (); private Integer id; private String brand; private String color; private Integer year; private String notes; private boolean used; static{ cars.add(new Car(0, "Honda","Yellow",1995,false,"Broken Brakes")); cars.add(new Car(1, "Volvo","Black",1973,true)); cars.add(new Car(1, "Audi","Silver",1987,false)); cars.add(new Car(1, "Renault","White",1963,true)); cars.add(new Car(1, "Volkswagen","Black",1985,true)); } public Car(Integer id, String brand, String color, Integer year, boolean used,String notes) { super(); this.id = id; this.brand = brand; this.color = color; this.year = year; this.used = used; this.notes = notes; } public Car(Integer id, String brand, String color, Integer year, boolean used) { this(id,brand,color,year,used,""); } public Car() {} public static List getAll(){ return cars; } public static Car get(final Integer id){ return (Car) CollectionUtils.find(cars, new Predicate() { public boolean evaluate(Object object) { return ((Car) object).getId().equals(id); } }); } public static Car store(Car p){ if(p.getId() == null){ Car maxUserId = Collections.max(cars, new Comparator () { public int compare(Car o1, Car o2) { return o1.getId().compareTo(o2.getId()); } }); p.setId(maxUserId.getId()+1); cars.add(p); }else{ cars.set(p.getId(), p); } return p; } public static void delete(Car p){ cars.remove(p); } //getter e setters }
Per sfruttare la DynaForm dobbiamo creare un’instanza di DynaFormModel
. Questo oggetto è composto da una serie di DynaFormRow
. Il nostro scopo è quindi quello di creare una DynaFormRow
per ogni proprietà accessibile dei nostri modelli, in maniera del tutto analoga a quanto analizzato nel precedente post. Per farlo utilizzeremo il seguente .
public class ReflectionDynaFormModelBuilder { private Class modelClass; private ComparatorpropertySortComparator; private Predicate propertyFilterPredicate; private Set excludedProperties; private static Set defaultExcludedProperties = new HashSet (0); private Map customBuilders = new HashMap (); public static Comparator DEFAULT_PROPERTY_COMPARATOR = new Comparator () { public int compare(PropertyDescriptor o1, PropertyDescriptor o2) { return o1.getName().compareTo(o2.getName()); } }; static{ defaultExcludedProperties.add("class"); } public ReflectionDynaFormModelBuilder(Class modelClass) { this.modelClass = modelClass; this.propertyFilterPredicate = PredicateUtils.truePredicate(); this.propertySortComparator = DEFAULT_PROPERTY_COMPARATOR; this.excludedProperties = new HashSet (0); } public ReflectionDynaFormModelBuilder setPropertyFilterPredicate(Predicate p){ this.propertyFilterPredicate = p; return this; } public ReflectionDynaFormModelBuilder setPropertySortComparator(Comparator c){ this.propertySortComparator = c; return this; } public ReflectionDynaFormModelBuilder setExcludedProperties(Set p){ this.excludedProperties = p; return this; } public ReflectionDynaFormModelBuilder putCustomBuilder(String name,FormControlBuilder builder){ this.customBuilders.put(name, builder); return this; } public ReflectionDynaFormModelBuilder putCustomBuilders(Map builders){ this.customBuilders.putAll(builders); return this; } public ReflectionDynaFormModelBuilder setExcludedProperties(String...p){ this.excludedProperties = new HashSet (0); for (String excludedProperty : p) { this.excludedProperties.add(excludedProperty); } return this; } public DynaFormModel build(){ DynaFormModel formModel = new DynaFormModel(); List propertyDescriptors = new ArrayList (Arrays.asList(PropertyUtils.getPropertyDescriptors(modelClass))); CollectionUtils.filter(propertyDescriptors, PredicateUtils.andPredicate(propertyFilterPredicate, new Predicate() { public boolean evaluate(Object object) { PropertyDescriptor propertyDescriptor = (PropertyDescriptor) object; return propertyDescriptor.getReadMethod() != null && propertyDescriptor.getWriteMethod() != null && !defaultExcludedProperties.contains(propertyDescriptor.getName()) && !excludedProperties.contains(propertyDescriptor.getName()); } })); Collections.sort(propertyDescriptors, propertySortComparator); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { DynaFormRow row = formModel.createRegularRow(); if(customBuilders.containsKey(propertyDescriptor.getName())){ customBuilders.get(propertyDescriptor.getName()).populateRow(row); }else{ //Default Row DynaFormLabel label = row.addLabel(propertyDescriptor.getName()); DynaFormControl input = row.addControl(new DynaPropertyModel(propertyDescriptor.getName()), propertyDescriptor.getPropertyType().getSimpleName().toLowerCase()); label.setForControl(input); } } return formModel; } }
Come potete vedere, per ogni proprietà abbiamo generato un DynaFormRow
, una DynaFormLabel
ma soprattutto un DynaFormControl
. Abbiamo anche creato un’istanza di DynaPropertyModel
: questa classe gestisce tutti i metadati della proprietà corrente. In questa prima versione contiene semplicemente il nome della proprietà stessa.
public class DynaPropertyModel implements Serializable{ private static final long serialVersionUID = 1L; private String name; public DynaPropertyModel() {} public DynaPropertyModel(String name) { super(); this.name = name; } //getter e setters }
Come utilizzare il Builder
Utilizzare il ReflectionDynaFormModelBuilder
è estremamente semplice, come possiamo vedere nel seguente managed bean.
@ManagedBean @ViewScoped public class BasicDetailExampleBean implements Serializable{ private static final long serialVersionUID = 1L; private Object model; private Class currentClass; private String currentClassName; private Integer id; private Boolean disabled = false; private DynaFormModel formModel; public final void onPreRender(){ try { currentClass = Class.forName(currentClassName); this.formModel = new ReflectionDynaFormModelBuilder(currentClass) .setExcludedProperties("id") .setPropertySortComparator(getPropertyComparator()) .putCustomBuilders(getCustomBuilders()) .build(); if(id != null){ this.model = MethodUtils.invokeExactStaticMethod(currentClass, "get", new Object[]{id}); }else{ this.model = currentClass.newInstance(); } } catch (Exception e) { throw new RuntimeException(e); } } protected ComparatorgetPropertyComparator() { return ReflectionDynaFormModelBuilder.DEFAULT_PROPERTY_COMPARATOR; } protected Map getCustomBuilders() { //No Custom return new HashMap (0); } public final String save(){ try { MethodUtils.invokeExactStaticMethod(currentClass, "store", new Object[]{this.model}); return "param.xhtml?class=" + currentClassName + "&faces-redirect=true"; } catch (Exception e) { throw new RuntimeException(e); } } //getter e setters }
Nella pagina XHTML, basta poi collegare il model appena creato alla DynaForm
.
Quella che otteniamo è una form dinamica a tutti gli effetti, basta cambiare i parametri class
e id
per cambiare forma e valori della form stessa.
Esempio di form dinamica: classe User
Esempio di form dinamica: classe Car
Integrazione con una Datatable
Dato che tutti i parametri della form dinamica sono gestiti tramite i view params, la sua integrazione con una datatable dinamica a sua volta, è immediato.
Create Edit View
Come potete vedere, con un solo managed bean riusciamo a gestire le operazioni CRUD per ogni classe di un piccolo progetto.
Come gestire le personalizzazioni
Questo micro framework accetta anche delle piccole personalizzazioni. Ad esempio per questioni di usabilità il campo notes
della classe Car
dovrebbe utilizzare una
al posto della
. Inoltre potrebbe essere comodo posizionare il campo in ultima posizione.
Possiamo forzare il comportamento del ReflectionDynaFormModelBuilder
per qualche specifica proprietà dei nostri model in questo modo:
@ManagedBean @ViewScoped public class AdvancedDetailExampleBean extends BasicDetailExampleBean{ private static final String NOTES_FIELD = "notes"; private static final long serialVersionUID = 1L; @Override protected ComparatorgetPropertyComparator() { return new Comparator () { public int compare(PropertyDescriptor first, PropertyDescriptor second) { if(NOTES_FIELD.equals(first.getName())){ return 1; }else if(NOTES_FIELD.equals(second.getName())){ return -1; }else{ return ReflectionDynaFormModelBuilder.DEFAULT_PROPERTY_COMPARATOR.compare(first, second); } } }; } @Override protected Map getCustomBuilders() { Map toReturn = new HashMap (0); toReturn.put(NOTES_FIELD, new FormControlBuilder() { public void populateRow(DynaFormRow row) { //We will show notes in a textArea DynaFormLabel label = row.addLabel(NOTES_FIELD); DynaFormControl input = row.addControl(new DynaPropertyModel(NOTES_FIELD), "text"); label.setForControl(input); } }); return toReturn; } }
In questo esempio abbiamo utilizzato una piccola interfaccia (FormControlBuilder
) agganciata tramite una Map
alla proprietà notes
, per generare un DynaFormControl
di tipo ‘text’, al posto del più canonico ‘string’. Il comportamento di default del builder è quello di utilizzare come tipo campo, il valore del metodo getSimpleName()
della classe della proprietà stessa. Inoltre abbiamo utilizzato un Comparator per fare in modo che notes
sia posizionato in fondo.
Potremmo anche aggiungere al progetto un framework per Dependecy Injection come o . In questo modo le customizzazioni potrebbero essere iniettate ed eviteremmo la ‘bruttura’ di dover creare un bean apposito.
Form custom per classe Car
Conclusioni
Con questo secondo post si conclude la prima versione di questo piccolo framework, che ci ha permesso con solo una manciata di classi di creare un’applicazione CRUD funzionante. Ovviamente questo codice non è pronto per entrare in produzione, come avrete notato il salvataggio dati è completamente ‘dummy’ e andrebbe sostituito con uno strato di persistenza vero. Nelle prossime release mi piacerebbe ampliare la classe DynaPropertyModel
per contenere maggiori informazioni sulla proprietà da gestire come required
, size
e maxlength
. In questo caso potremmo sfruttare le annotation della per popolare questi metadati a run time. Un’altra possibile implementazione potrebbe essere quella di gestire delle convezioni sui nomi dei campi: ad esempio un campo notes
potrebbe sempre essere gestito con una textarea e posizionato per ultimo.
Ovviamente qualsiasi consiglio/miglioria è ben accetta e potete contribuire al progetto collegandovi al mio repository GitHub. Alla prossima!