lundi 27 décembre 2010

JSF : Créer un custom converter paramétrable

Je viens de tomber devant un problème qui s'avère récurrent quand lorsque l'on "blinde" une appli.

Mon contexte technique est le suivant : JSF 1.2 (myfaces) + Tomahawk12 + Facelets 1.0 je n'ai bien entendu pas le droit d'utiliser la sandbox de myfaces.

L'utilisation d'un custom converter via le tag <f:converter id="myConverterId"/> ne permet pas d'envoyer un paramètre au converter.


Mon cas : Créer un converter permettant de tronquer une chaine de caractères à une longueur définie directement dans ma page.

Seulement 3 étapes et 10 minutes sont nécessaires :

_ Créer la classe du converter
_ Créer un custom tag via Facelet mapé sur ce converter
_ Créer sa page

1 - Créer la classe

Cet article n'étant pas un totu sur les converters, je vais me concentrer sur ce qui nous intéresse : le paramétrage.
Créez votre converter "as usual" et offrez lui en plus les 2 choses suivantes :
_ le/les paramètre(s) : propriété privée avec accesseurs publics
private Integer maxSize = -1;

_ une propriété statique nommée CONVERTER_ID
public static final String CONVERTER_ID = "maxLengthConverter";

C'est tout, votre classe ressemble à ça :





public class MaxLengthConverter implements Converter{

    public static final String CONVERTER_ID = "maxLengthConverter";
    static final Logger logger =
        LoggerFactory.getLogger(MaxLengthConverter.class.getName());
    protected boolean _transcient;
    private Integer maxSize = -1;
   
    /*
     * (non-Javadoc)
     * @see
     * javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext
     * , javax.faces.component.UIComponent, java.lang.String)
     */
    public Object getAsObject(FacesContext arg0, UIComponent arg1, String arg2)
        throws ConverterException
    {
        String val = (String) arg2;
        if (maxSize>0 && val.length() > maxSize)
        {
            val = val.substring(0, maxSize) + "...";
        }
        return val;
    }
    /*
     * (non-Javadoc)
     * @see
     * javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext
     * , javax.faces.component.UIComponent, java.lang.Object)
     */
    public String getAsString(FacesContext arg0, UIComponent arg1, Object arg2)
        throws ConverterException
    {
        if (arg2 instanceof String)
        {
            String val = (String) arg2;
            if (maxSize>0 && val.length() > maxSize)
            {
                val = val.substring(0, maxSize) + "...";
            }
            return val;
        }
        else
        {
            logger.error("La valeur passée au converter n'est pas une String.");
            ConverterException expt =
                new ConverterException(
                    new ClassCastException(
                        "MaxLengthConverter n'accepte que des String en paramètre."));
            throw expt;
        }
    }
    /**
     * Accesseur de maxSize
     *
     * @return maxSize
     */
    public final Integer getMaxSize()
    {
        return maxSize;
    }
    /**
     * Setter de maxSize
     *
     * @param maxSize maxSize à définir
     */
    public final void setMaxSize(Integer maxSize)
    {
        this.maxSize = maxSize;
    }
}


2- Déclarer le converter à JSF

Dans le FacesConfig, déclarez votre converter comme suit :


<converter>
  <converter-id>maxLengthConverter</converter-id>
  <converter-class>dwdwdw.examples.converters.MaxLengthConverter</converter-class>
  <property>
   <description>-1 = no max length
&gt;0 = maximum length of the output string.</description>
   <property-name>maxSize</property-name>
   <property-class>java.lang.Integer</property-class>
   <default-value>-1</default-value>
   <suggested-value>-1</suggested-value>
  </property>
 </converter>


Ici, il ne faut pas oublier de déclarer la propriété qui rendra notre converter paramétrable depuis la page JSF.


3 - Créer le custom tag

Grâce à Facelet, il n'y a rien de plus simple, créez simplement un fichier dont le nom se termine par ".taglib.xml" dans /META-INF et définissez votre tag. Facelet ne requiert pas de classe de tag.

Votre fichier ressemblera à celui-ci :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
   "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
   "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
   <namespace>http://fr.gouv.impots.vulcain/</namespace>
   <tag>
      <tag-name>trimMaxSize</tag-name>
      <converter>
         <converter-id>maxLengthConverter</converter-id>
      </converter>
   </tag>
</facelet-taglib>

Dans votre web.xml, n'oubliez pas de déclarer votre taglib :



  facelets.LIBRARIES  /META-INF/mataglib.taglib.xml


4- Créer sa page

Il ne vous reste maintenant plus qu'à créer votre page sans oublier de déclarer votre taglib :


<!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:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:t="http://myfaces.apache.org/tomahawk"
      xmlns:custom="
http://duwebduwebduweb.com/" >

......
      <t:outputText value="#{item.description}">
          <vulcain:trimMaxSize maxSize="50"/>
      </t:outputText>
......



5 - Compatibilité avec t:commandSortHeader

Lorsque vos pages ont un état géré par JSF, et notamment par MyFaces via le tag t:StaveState ou t:commandSortHeader, votre converter doit pouvoir stocker et restaurer son état.

Pour cela il doit implémenter l'interface javax.faces.component.StateHolder. Pour cela vous devrez ajouter une propriété boolean _transcient (nécessaire au focntionnement du saveState) et redéfinir certaines fonctions comme suit :



    protected boolean _transcient;

.....
    public boolean isTransient()
    {
        return _transcient;
    }

    public void restoreState(FacesContext arg0, Object arg1)
    {
        Object[] values = (Object[])arg1;
        this.maxSize = (Integer)values[0];
    }

    public Object saveState(FacesContext arg0)
    {
        Object[] values = new Object[1];
        values[0] = this.maxSize;
        return values;
    }

    public void setTransient(boolean arg0)
    {
        _transcient = arg0;
    }

Voilà, tout y est. Enjoy !