JSF2: valueChangeListerer e valori null

Problema: setto a null il valore di un inputText tramite jQuery, faccio submit della pagina e scatta la validazione tramite validatore custom. Se la validazione fallisce viene riportato nel campo messo a null l’ultimo valore valido precedente.

Causa: da una discussione trovata su http://stackoverflow.com/questions/3933786/jsf-2-bean-validation-validation-failed-empty-values-are-replaced-with-las pare sia un *bug* di Mojarra:

settando la proprietà nel web.xml:

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>

si verifica un bug in HtmlBasicRenderer#getCurrentValue() in Mojarra:

if (component instanceof UIInput) {
    Object submittedValue = ((UIInput) component).getSubmittedValue();
    if (submittedValue != null) {
        // value may not be a String...
        return submittedValue.toString();
    }
}

String currentValue = null;
Object currentObj = getValue(component);
if (currentObj != null) {
    currentValue = getFormattedValue(context, component, currentObj);
}
return currentValue;

Normalmente, il valore inviato è impostato su null quando il componente UIInput viene convertito e validato con successo. Quando JSF è in procinto di visualizzare nuovamente il valore, prima controlla se il valore inserito non sia null prima di procedere per visualizzare di nuovo il valore di modello. Tuttavia, con questo parametro di contesto, è nullo, invece di una stringa vuota quando non è valido e così sarà sempre visualizzare nuovamente il valore del modello originale quando si rimuove il valore iniziale di un campo obbligatorio.

Soluzione: la parte jQuery setta il valore *stringa vuota* invece di null (nel caso specifico la soluzione funziona perché il test sul valore è effettuato tramite il metodo di utilità org.apache.commons.lang3.StringUtils.isNotBlank sul valore dell’input).

The Java 6 EE Tutorial – Validazioni in JSF

Le validazioni JSF si ottengono in diversi modi:

  1. Usando JSF validator tag da annidare nella dichiarazione dei componenti nella view: viene fatto il binding tramite value expression ad una proprietà del backing bean.
  2. Creando un metodo nel backing beane facendovi riferimento nel componente tramite l’attributo validator.
    1. Il metodo del backing bean che esegue la validazione deve accettare un FacesContext, il componente i cui dati devono essere validati e il dato da validare ( stessa signature del metodo validate dell’interfaccia Validator). Possono essere validati solo valori di componenti UIInput o di suoi discendenti).
  3. Usando Bean Validator (NEW IN Java 6). I constraints per la validazione sono definiti tramite annotation su campi, metodi o classi di JavaBean component (quindi anche backing bean). Sono disponibili constraints built-in e in più se ne possono definire altre custom. Si può mettere più di un constraint sullo stesso elemento.

Esempi

JSF validator tag

<h:inputText id="Username" value="#{UserBean.userName}">
   <f:validateLength minimum="6" maximum="15"/>
</h:inputText>

Metodo nel backing bean

public void validateEmail(FacesContext context,UIComponent toValidate, Object value) {
  String message = "";
  String email = (String) value;
  if (email.contains(’@’)) {
    ((UIInput)toValidate).setValid(false);
    message = CoffeeBreakBean.loadErrorMessage(context,CoffeeBreakBean.CB_RESOURCE_BUNDLE_NAME,    "EMailError");
    context.addMessage(toValidate.getClientId(context),new FacesMessage(message));
  }
}

Nella view

<h:inputText value="#{userBean.email}" validator="#{userBean.validateEmail}">
</h:inputText>

Bean Validator 

Built-in

public class Name {
  @NotNull
  @Size(min=1, max=16)
  private String firstname;
  @NotNull
  @Size(min=1, max=16)
  private String lastname;
}

Validatori built-in disponibili

Custom

@NotNull
@Size(min = 3)
@FirstUpper
public String getName() {
  return name;
}
Definizione della annotation
package me.m1key.jsf.constraints;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Documented
@Constraint(validatedBy = FirstUpperValidator.class)
@Target( { METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)

public @interface FirstUpper {
  String message() default "{me.m1key.jsf.constraints.FirstUpper.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}
Implementazione del validatore
package me.m1key.jsf.constraints;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class FirstUpperValidator implements ConstraintValidator<FirstUpper, String> {
@Override
public void initialize(FirstUpper firstUpper) {
 // See JSR 303 Section 2.4.1 for sample implementation.
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
  if (value == null || value.length() == 0) {
    return true;
  }
  return value.substring(0, 1).equals(value.substring(0, 1).toUpperCase());
}
}
See more at:
http://viralpatel.net/blogs/javaserver-faces-jsf-validation-tutorial-error-handling-jsf-validator/
http://www.mkyong.com/jsf2/custom-validator-in-jsf-2-0/
http://blog.m1key.me/2010/07/custom-validation-with-bean-validation.html

The Java 6 EE Tutorial – Expression Language

Reference ad oggetti

  • JavaBean components
  • Collections
  • Java SE enumerated types
  • Implicit object

Si scrive una espressione usando una variabile, che è il nome dell’oggetto. Il web container valuta la variabile cercando il suo valore sulla base del comportamento del metodo PageContext.findAttribute(String) dove String = nome variabile. La ricerca viene fatta dentro a:

    • la pagina
    • la request
    • la session
    • l’application scope.

Se non trovato torna null.

The Java EE 6 Tutorial – JSF Lifecycle

JSF Lifecycle

Execute Phase:

  • costruzione/ripristino della view
  • applicazione dei valori dei parametri della request
  • conversioni e validazioni
  • aggiornamento dei backing bean con i valori dei componenti
  • invocazione logica applicativa

Render Phase:

  • generazione output, tipicamente HTML e XHTML, che può essere “letto” dal browser

Dettaglio Lifecycle

Client request
--> compilazione Facelets (parte di presentazione)
--> esecuzione Facelets 
                       ---> costruzione component-tree
                       ---> component-tree aggiunto al FacesContex
--> popolamento del component-tree con i valori presenti nel backing-bean, recuperati tramite espressioni EL
--> costruzione di una nuova view sulla base del component-tree
--> render della view come response al client
--> distruzione automatica dal component-tree 
    (ad una successiva richiesta, il component-tree è ricostruito e viene applicato lo stat     o salvato)

The Java EE 6 Tutorial – Java Server Faces technology

JSF

Framework di componenti server-side per costruire web application con tecnologia Java.

Offre:

  • API per rappresentare i componenti e mantenere il loro stato
  • tag library per aggiungere componenti alle pagine web e per connettere i componenti con gli oggetti server-side

Immagine

La pagina myfacelet.xhtml è composta da tag di componenti JSF.

I tag dei componenti sono usati per aggiungere componenti alla view myUI, che è la rappresentazione server-side della pagina.

A seguito di una request del client, la view è renderizzata come response.

Rendering: il web container genera output (XHTML o HTML) sulla base della view server-side.

JSF, MyFaces, Facelts, PrimeFaces, jQuery….che confusione!

Tutti i componenti sono renderizzati come HTML/XHTML come response.

Interazione JSF – jQuery:

1) i componenti vengono *trasformati* in html

2) i metodi javascript dichiarati con jQuery possono essere eseguiti (sono eseguiti sul browser)

Approfondimenti

JSF 2.0 Reality Check – TechJava.

JSF 2.0 novità e caratteristiche

JSF 2.0 – selectItems con enum

Goal: aggiungere in una pagina JSF – Facelts un selectOneRadio che prenda i valori da un enum.

public enum PersonGender {
  M("Male"),
  F("Female");
  private String description;
  private PersonGender(String description) {
   this.description = description;
  }
  public String getDescription() {
   return description;
  }
  public void setDescription(String description) {
   this.description = description;
  }
 }

Per poter visualizzare i valori nella pagina, è necessario un bean che esponga un metodo che torni il .values() dell’enum.

Ho creato un bean di sessione generico, in modo che possa essere riusato da diverse pagine e per diversi enum:

@ManagedBean
@SessionScoped
public class EnumCollectorBean {
public PersonGender[] getPersonGenderValues() {
 return PersonGender.values();
}
}

Ora si può aggiungere il riferimento al metodo getPersonGenderValues nella pagina:

si può mettere un riferimento di tipo immediate ${} (valutato al primo render della pagina) trattandosi di valori costanti.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core">

<ui:composition template="/WEB-INF/templates/BasicTemplate.xhtml">
 <ui:define name="content">
  <h:outputLabel value="Hell-O #{loginBean.name}"></h:outputLabel>
  <br />
  <br />
  <h:panelGroup>
   <h:outputLabel value="Please, insert more information about you" />
    <h:panelGrid columns="2">
     <h:outputLabel value="Gender" />
     <h:selectOneRadio id="gender" value="##{loginBean.person.gender}">
      <f:selectItems value="${enumCollectorBean.personGenderValues}" var="g" 
       itemLabel="#{g.description}" />
     </h:selectOneRadio>
     <h:outputLabel value="Address" />
     <h:inputTextarea value="#{loginBean.person.address}" />
     <h:outputLabel value="Phone Number" />
     <h:inputText value="#{loginBean.person.phoneNumber}" />
    </h:panelGrid>
   </h:panelGroup>
  </ui:define>
 </ui:composition>
</html>