Tutoriel sur les Composite-Components de JSF2

L'objectif du tutoriel est de vous faire découvrir les composite-components en JSF2 et les possibilités avancées au travers d'un exemple de gestion de clé externe très utile dans bien des applications.

Les sources de l'exemple sont disponibles ici.

Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum Commentez Donner une note à l'article (5).

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Présentation

Arrivé avec Facelets et la version 2.0 de JSF, le composite-component est un terme désignant une forme de template qui agit comme un composant graphique (UI-Component) réutilisable.

Il utilise une déclaration XML plutôt que du Java et est accessible sous le nom de domaine (namespace) http://java.sun.com/jsf/composite.

Il est placé dans un répertoire « resources ».

II. Création d'un composant

La mise en œuvre est très simple, il suffit de :

  • créer un répertoire resources à la racine du projet web (/WebContent/resources) ;
  • créer un fichier d'extension xhtml pour la définition d'un composant.

On peut bien sûr structurer le répertoire « resources » en fonction de ses besoins. Il pourrait être judicieux de créer un sous-répertoire « composants » sous « resources ».

Le fichier de définition aura la structure suivante

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
<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:composite="http://java.sun.com/jsf/composite">

<composite:interface>
    // Définition de l'interface du composant
</composite:interface>

<composite:implementation>
    // Définition de l'implémentation
</composite:implementation>
</html>

II-A. Définition de l'interface

La partie interface définit le « contrat » du composant. Dans ce contrat, on trouvera des paramètres sous forme d'attributs de l'interface et/ou un type associé au composant.

Les attributs de la balise <composite:interface> (les attributs sans grande importance sont grisés)

Attribut

Définition

name

Déclaration d'un attribut du composant.

componentType

Type (classe) associé au composant. Lorsque le composant gère plusieurs éléments graphiques, les encapsuler dans une classe spécialisée peut s'avérer extrêmement utile.

displayName

Nom à afficher par une palette de composants.

shortDescription

Description courte.

prefered

S'agit-il d'un composant « préféré » ?

expert

Le composant est uniquement pour des utilisateurs experts.

La partie interface pourrait être vide s'il n'y avait aucun attribut à passer au composant, mais il faut bien reconnaître que l'intérêt serait des plus réduit, une inclusion de page ferait largement l'affaire dans ce cas.

Généralement, on y définit les attributs (paramètres) de configuration du composant.

La syntaxe générale est :

 
Sélectionnez
<composite:élément attribut1="valeur1" ... attributN="valeurN">

Les éléments les plus utiles sont :

Élément

Définition

Attribue

Déclaration d'un attribut du composant.

valueHolder

Un élément (ou plusieurs) du composant expose une implémentation de javax.faces.component.ValueHolder.

editableValueHolder

Un élément (ou plusieurs) du composant expose une implémentation de javax.faces.component.EditableValueHolder.

clientBehavior

Un élément (ou plusieurs) du composant expose une implémentation de javax.faces.component.behavior.ClientBehavior.

actionSource

Un élément (ou plusieurs) du composant expose une implémentation de javax.faces.component.ActionSource2

Liste des attributs par élément.

composite:attribute

Attribut

Description

Name

Nom de l'attribut.

Default

Valeur par défaut de l'attribut.

Required

Spécifie si l'attribut est obligatoire.

Method-signature

Signature de la méthode pour un attribut de type action « MethodExpression ».
Les classes des paramètres doivent être qualifiées.
Si l'attribut « type » est présent, ce paramètre est ignoré.
Exemple : <composite:attribute name=« do » method-signature=« java.lang.String doSomething() »/>.

Type

Un élément (ou plusieurs) du composant expose une implémentation de javax.faces.component.ActionSource2

composite:valueHolder

Interface utile pour gérer une valeur locale ainsi qu'accéder aux données d'un modèle tiers par une expression et convertir un type String avec un type natif différent (converter).

Attribut

Description

Name

Nom de l'attribut. Ce nom peut être utilisé dans un attribut « for » d'un composant graphique. Si l'attribut « target » n'est pas précisé, ce nom représente également l'id du composant cible.

Targets

Liste des composants cibles (séparés par un blanc).

composite:editableValueHolder

Interface dérivée de ValueHolder, ajoutant le support des validators et des événements de changement de valeur (ValueChangeListener).

Attribut

Description

Name

Nom de l'attribut. Ce nom peut être utilisé dans un attribut « for » d'un composant graphique. Si l'attribut « target » n'est pas précisé, ce nom représente également l'id du composant cible.

Targets

Liste des composants cibles (séparés par un blanc).

composite:clientBehavior

Permet à la page utilisatrice de préciser une action Ajax (ou JavaScript) sur un événement.

Attribut

Description

Name

Nom de l'attribut. Ce nom peut être utilisé dans un attribut « for » d'un composant graphique. Si l'attribut « target » n'est pas précisé, ce nom représente également l'id du composant cible.

Targets

Liste des composants cibles (séparés par un blanc).

Event

Nom de l'événement géré.

Default

Si « true », la page utilisatrice pourra omettre l'attribut « event ».

composite:actionSource

Permet au composant d'exécuter une action définie par la page utilisatrice.

Attribut

Description

Name

Nom de l'attribut. Ce nom peut être utilisé dans un attribut « for » d'un composant graphique. Si l'attribut « target » n'est pas précisé, ce nom représente également l'id du composant cible.

Targets

Liste des composants cibles (séparés par un blanc).

II-B. Définition de l'implémentation

La partie implémentation représente la partie visible du composant.

On y retrouve des éléments HTML ou d'autres composants de bibliothèques graphiques comme Primefaces, Richfaces, etc.

L'implémentation aura accès aux attributs de l'interface au travers de l'expression : #{cc.attrs.nom_attribut}

Un composant simple pour afficher une image associée à un objet java.lang.Boolean pourrait ressembler à ceci

 
Sélectionnez
<?xml version='1.0' encoding='UTF-8'&#160;?>
<!DOCTYPE html PUBLIC «&#160;-//W3C//DTD XHTML 1.0 Transitional//EN&#160;» «&#160;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&#160;»>

<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:composite="http://java.sun.com/jsf/composite"
	xmlns:c="http://java.sun.com/jsp/jstl/core"
	xmlns:p="http://primefaces.org/ui">

<composite:interface>
	<composite:attribute name="value" required="true" type="java.lang.Boolean"/>
</composite:interface>

<composite:implementation>
	<center>
		<h:graphicImage value="/resources/images/yes.png" rendered="#{cc.attrs.value.booleanValue() == true}" />
	</center>
</composite:implementation>
</html>

Même si ce composant peut paraître inutile, il a l'avantage d'abstraire la représentation d'une valeur booléenne dans toutes les pages où on en aurait besoin.

Ainsi, sans avoir autre chose à modifier que le composant, on pourrait décider dans un premier temps que seule la valeur « true » serait représentée (cas de l'exemple).

Une autre règle plus tardive dans le cycle de développement pourrait faire qu'il faut représenter la valeur « false » également.

III. Utilisation d'un composant dans une page

Maintenant que nous avons vu comment concevoir des composants, reste bien sûr à les utiliser.

Pour ça, il suffit que la page utilisatrice référence le/les composants de l'application par leur namespace.

En fonction de la structuration utilisée, le référencement pourra être différent, mais la racine sera toujours la même xmlns:un_nom="http://java.sun.com/jsf/composite".

Exemple (avec template) utilisant un sous-répertoire dédié « component »

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
<ui:composition 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"
    xmlns:util="http://java.sun.com/jsf/composite/component"
    xmlns:p="http://primefaces.org/ui" template="/template/template.xhtml">
</ui:composition>

Dans cet exemple, à chaque fois que la page aura besoin d'un de nos composants, elle le référencera via <util:nom_composant>.

Le nom_composant est en pratique le nom du fichier de définition sans l'extension xhtml.

Dans la bibliothèque de composants de notre exemple (joint), on peut voir deux définitions :

  • foreignKey-Constr.xhtml ;
  • graphicBoolean.xhtml.

On les référencerait dans les pages par :

 
Sélectionnez
1.
2.
<util:graphicBoolean attribut1="valeur1">
<util:foreignKey-Constr attribut1="valeur1"  attribut2="valeur2">

Les valeurs des attributs sont soit des constantes, soit des expressions

 
Sélectionnez
attribut1="#{monManagedBean.unAttribut"
attribut2="150"

IV. Étude de cas complexe : les clés étrangères

IV-A. Présentation

Pour approfondir et voir le grand intérêt des composite-components, nous allons réaliser un composant de gestion de clé étrangère.

En effet, dans nombre d'applications dites de « gestion » se pose la question de la centralisation et la matérialisation d'une référence externe (clé étrangère ou foreign-key), ou plus généralement, d'une entité applicative.

Un article, par exemple, sera référencé dans beaucoup de tables (stock, nomenclature, catalogue constructeur, etc.), il en va de même pour un constructeur, un client, un fournisseur, etc.

Partout où nous ferons référence à ces éléments se poseront les questions :

  • je le choisis comment ?
  • j'affiche quoi ?

Un autre aspect lié à ce composant, en fonction des cas, nous voudrons :

  • qu'il ne soit pas modifiable ;
  • qu'on ait accès qu'aux informations de décodification (sur une ou plusieurs lignes) ;
  • qu'on puisse lui appliquer des contrôles différents en fonction de l'appel ;
  • qu'on puisse agrandir ou rétrécir la zone de sélection et/ou la zone décodification.

Le contrat de base est déjà bien compliqué, mais nous verrons que le composite-component est tout à fait capable de pallier tout ceci.

IV-B. La base de données

Pour simplifier la compréhension au maximum, nous prendrons l'exemple d'un composant lié à un constructeur. Ce constructeur sera utilisé pour sélectionner des articles.

La base de données se limitera donc à deux tables :

Image non disponible

Scripts de création

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
CREATE TABLE CONSTR (          
  UID            INTEGER AS IDENTITY NOT NULL,
  CODE            VARCHAR(10) NOT NULL, 
  NOM            VARCHAR(80),          
  ADRESSE1        VARCHAR(128),         
  ADRESSE2        VARCHAR(128),         
  ADRESSE3        VARCHAR(128),         
  CP             VARCHAR(80),          
  VILLE        VARCHAR(128),         
  PRIMARY KEY (UID)                
)                              

CREATE TABLE ARTICLE (                      
  UID            INTEGER AS IDENTITY NOT NULL,             
  CODE            VARCHAR(10) NOT NULL,             
  DESIGNATION     VARCHAR(80),                      
  UID_CONSTR        INTEGER,                          
  PRIMARY KEY (UID),                            
  CONSTRAINT FK_ARTICLE_REL_CONSTR_1            
  FOREIGN KEY (UID_CONSTR)                      
  REFERENCES CONSTR (UID) ON DELETE RESTRICT
  ON UPDATE RESTRICT                            
)

IV-C. L'aspect du composant

Notre composant ressemblera à ceci (en vert, délimitation du composant) :

Image non disponible

Lorsque l'utilisateur cliquera sur le bouton, une liste de sélection multicritère sera affichée, son apparence sera la suivante :

Image non disponible

Si l'utilisateur connaît le code de son constructeur (qui représente une clé unique), il pourra la saisir directement dans la zone « clé » et le composant décodifiera sa valeur.

IV-D. Partie interface

Pour notre contrat (interface), nous aurons donc besoin :

  • d'un attribut Constr (représentant le constructeur) ;
  • d'un attribut Boolean pour définir si le composant est en lecture seule ;
  • d'un attribut Boolean pour définir si nous voulons uniquement la décodification ;
  • d'un attribut String pour définir la largeur de la partie clé ;
  • d'un attribut String pour définir la largeur de la partie décodification.

Comme nous aurons besoin d'agir sur la modification du constructeur, nous ajouterons :

  • un élément clientBehavior pour l'événement « change ».

Nous aurons également besoin d'affecter et récupérer la valeur Constr et de lui appliquer un contrôle de validité au besoin, nous ajouterons :

  • un élément editableValueHolder lié à la partie clé.

Comme notre composant gère trois parties distinctes :

  • saisie directe d'une clé constructeur ;
  • bouton d'appel d'une liste de sélection ;
  • label associé au constructeur (décodification),

nous associerons notre composant à une classe spécialisée nommée foreignKeyConstrComponent, nous y reviendrons par la suite.

Notre interface sera donc la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
<composite:interface componentType="foreignKeyConstrComponent">
    <composite:attribute name="value" required="true" type="test.ejb.client.entity.Constr"/>
    <composite:attribute name="readOnly" default="false" type="java.lang.Boolean"/>
    <composite:attribute name="labelOnly" default="false" type="java.lang.Boolean"/>
    <composite:attribute name="keyWidth" default="100px"/>
    <composite:attribute name="labelWidth" default="300px"/>
   <composite:clientBehavior name="change" event="change" targets="key" />
     <composite:editableValueHolder name="key" targets="key"/>
</composite:interface>

IV-E. Classe spécialisée du composant

Tel qu'est défini notre composant, avec ses trois parties distinctes (clé, bouton, label), nous pourrons utiliser cette structure dans beaucoup de cas (sinon tous).

Seuls les éléments réagissant avec notre classe devront être référencés ici, le bouton ne servant qu'à ouvrir la liste, nul besoin de l'associer.

Il paraît intéressant de factoriser ces éléments dans une classe abstraite ainsi que certaines fonctions génériques comme :

  • l'envoi de message à l'application ;
  • le traitement des validators associés (s'il y en a) ;
  • l'invalidation suite à une erreur,

ce qui permettra de se concentrer uniquement sur les traitements spécifiques dans chaque composant.

IV-E-1. Classe abstraite de factorisation

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
public abstract class AbstractForeignKeyComponent extends UIInput implements NamingContainer
{
    public final Logger logger = Logger.getLogger(getClass().getSimpleName());
    protected UIInput key;
    protected UIOutput lbl;

    /*
     * ----- getter / setter à placer ici
     */

    public abstract void formatValue();
    public abstract void updateValue(AjaxBehaviorEvent event);


    @Override
    public String getFamily()
    {
        return UINamingContainer.COMPONENT_FAMILY;
    }

    /**
     * Début de l'encodage du composant
     */
    @Override
    public void encodeBegin(FacesContext facesContext) throws IOException
    {
        logger.log(Level.FINEST, "encodeBegin(FacesContext)");
        formatValue();
        super.encodeBegin(facesContext);
    }


    /**
     * Envoyer un message d'application
     * @param target
     * @param message
     */
    public void sendFacesMessage(String target, FacesMessage message)
    {
        if (message != null)
        {
            FacesContext facesContext = FacesContext.getCurrentInstance();
            facesContext.getExternalContext().getFlash().setKeepMessages(true);
            facesContext.addMessage(target, message);
        }
    }

    /**
     * Validation (si nécessaire)
     */
    @Override
    public void validate(FacesContext facesContext)
    {
        logger.log(Level.FINEST, "validate(FacesContext)");

        /*
         * On traite tous les validators enregistrés
         */
        for (Validator validator : getValidators())
        {
            try
            {
                validator.validate(facesContext, this, getValue());
            }
            catch (ValidatorException e)
            {
                invalidValue();
                sendFacesMessage(null, e.getFacesMessage());
            }
        }
    }

    /**
     * Valeur incorrecte
     */
    public void invalidValue()
    {
        logger.log(Level.FINEST, "invalidValue()");
        if (getKey().getValue() != null)
        {
            setValid(false);
            key.setValid(false);
        }
    }
}

Quelques explications :

  • elle implémente NamingContainer : un composite-component doit toujours le faire pour définir sa famille et ainsi éviter des conflits et doublons dans le conteneur. En pratique, la famille du composant sera UINamingContainer.COMPONENT_FAMILY ;
  • elle étend UIInput : dans notre cas, le composant gère des éléments d'entrées/sorties, nous allons donc spécialiser un composant existant prévu à cet effet javax.faces.component.UIInput ;
  • elle contient une propriété « key » de type UIInput : pour gérer la valeur clé de notre objet géré (pour l'exemple, un Constr). Le formatage sera réalisé par la classe d'implémentation spécifique via la fonction formatValue() ;
  • elle contient une propriété « lbl » de type UIOutput : pour afficher la décodification de l'objet géré. Le formatage sera réalisé par la classe d'implémentation spécifique via la fonction formatValue().

Les méthodes de notre classe

  • encodeBegin(FacesContext) : elle est appelée à la création du composant. Dans notre cas, il faut lancer le traitement d'affectation de la valeur en cours aux différents éléments de notre composant (la clé et la décodification) si besoin. Ceci est réalisé par la méthode formatValue().
  • (abstract) formatValue() : méthode abstraite à définir par la classe d'implémentation qui servira à affecter la partie clé de l'objet géré à notre champ d'entrée « key » et la décodification au champ de sortie « lbl ».
  • (abstract) updateValue(AjaxBehaviorEvent event) : méthode abstraite à définir par la classe d'implémentation qui servira à positionner la valeur sélectionnée. L'origine de cette valeur est soit la liste de sélection, soit la saisie de la clé directement.
  • sendFacesMessage(String target, FacesMessage message) : elle envoie un message à l'utilisateur, elle ne fait rien de particulier.
  • validate(FacesContext facesContext) : elle est appelée pour valider un changement de valeur ou la valeur en cours lors de la soumission du formulaire de la page. Tous les validators liés au composant sont appelés, en cas d'erreur, nous invalidons la valeur par la méthode invalidValue() et nous envoyons un message à l'utilisateur par sendFacesMessage(...).
  • invalidValue() : elle signale visuellement à l'utilisateur que la valeur est incorrecte. Dans notre exemple, la couleur de la bordure de l'élément « clé » passe en rouge.

IV-E-2. La classe d'implémentation de notre composant

Il ne reste plus qu'à implémenter les deux méthodes spécifiques liées à l'objet géré (Constr) et à enregistrer notre composant sous un nom par l'annotation @FacesComponent.

ce nom doit être identique à l'attribut componentType de la balise <composite:interface componentType=« foreignKeyConstrComponent »> de notre définition de composant (page xhtml).

Voici le code de notre classe :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
/**
 * Composant : Clé externe
 * @author Olivier BUTTERLIN
 */
@FacesComponent("foreignKeyConstrComponent")
public class ForeignKeyConstrComponent extends AbstractForeignKeyComponent
{

    /**
     * La valeur change, mise à jour des composants
     * @param event
     */
    public void updateValue(AjaxBehaviorEvent event)
    {
        try
        {
            logger.log(Level.FINEST, "updateValue(AjaxBehaviorEvent) ");
            if (event instanceof SelectEvent)
            {
                Constr constr = (Constr) ((SelectEvent) event).getObject();
                setValue(constr);
                RequestContext.getCurrentInstance().execute("callChangeFunction('" + key.getClientId() + "')");
            }
            else
            {
                if (key.getValue() != null && key.getValue().toString().trim().length() > 0)
                {
                    String keyValue = (String) key.getValue();
                    Constr constr = FKComponentEJBLocator.getInstance().getConstrFacadeLocal().getConstrParCode(keyValue);
                    setValue(constr);
                }
                else
                {
                    super.setValue(null);
                    updateModel(FacesContext.getCurrentInstance());
                    formatValue();
                }
            }
        }
        catch (Exception e)
        {
            logger.log(Level.SEVERE, e.toString());
        }
    }


    /**
     * Formattage de la valeur
     */
    public void formatValue()
    {
        Constr constr = null;
        if (getValue() instanceof Constr)
        {
            constr = (Constr)getValue();
        }

        String label = "";
        if (constr != null)
        {
            key.setValue(constr.getCode());
            label = constr.getNom();
        }
        else
        {
            key.setValue(null);
        }

        lbl.setValue(label);
    }
}

Les méthodes de notre classe

  • updateValue(AjaxBehaviorEvent event)

Cette méthode peut être appelée de deux manières :

  1. Lors de la sélection d'une ligne dans la liste de sélection ;
  2. Lors de la modification de la partie « clé » de notre composant.

Pour distinguer les deux cas, on se base sur le type de l'événement reçu. S'il s'agit d'un événement de type SelectEvent, nous venons de la liste de sélection.

Cas 1 

Notre liste gérant des objets de type Constr, il suffit d'affecter au composant l'objet passé en paramètre.

Pour forcer l'événement « change » du composant (la page utilisatrice peut avoir défini une action particulière), nous appelons une méthode JavaScript par la ligne

 
Sélectionnez
RequestContext.getCurrentInstance().execute("callChangeFunction('" + key.getClientId() + "')");

Cette méthode mériterait d'être retravaillée, mais pour l'instant, je n'ai pas trouvé mieux…

key.getClientId() représente l'id unique du composant UIInput « key » de notre composant. Le code de cette fonction est très simple, il suffit de l'inclure à la page utilisatrice ou plus simplement au template de pages de l'application.

 
Sélectionnez
function callChangeFunction(id)
{
    try
    {
        var el = document.getElementById(id);
        $(el).change();
    }
    catch (e) {}
}

Cas 2 

La partie « clé » du composant est une chaîne de caractères représentant le code d'un constructeur, il faut aller chercher l'objet Constr (s'il existe) depuis la base de données et affecter cet objet au composant.

  • formatValue()

La méthode getValue() nous renvoie théoriquement un objet de type Constr, mais aucune certitude n'est possible à ce niveau d'où le test instanceOf.

Si l'objet retourné est du bon type et non null, il suffit de formater en fonction de nos besoins la chaîne de caractères qui représente la décodification du constructeur et de l'affecter au composant UIOutput « lbl ».

IV-F. Partie implémentation

Il s'agit maintenant d'implémenter la représentation graphique de notre composant qui est définie par la balise <composite:implementation>. Dans l'exemple, nous distinguons deux blocs distincts :

  1. L'affichage de la clé, le bouton et la décodification ;
  2. La liste de sélection.

IV-F-1. Partie affichage

 
Sélectionnez
<!-- Affichage complet -->
    <p:panelGrid rendered="#{!cc.attrs.labelOnly}">
        <p:row>
            <p:column styleClass="ui-foreignKey-column">
                <p:inputText id="key" binding="#{cc.key}" 
                            disabled="#{cc.attrs.readOnly}" 
                            style="width:#{cc.attrs.keyWidth}">
                    <p:ajax event="change"  listener="#{cc.updateValue}" 
                            process="@this" update="key btn txt"/>
                </p:inputText>
            </p:column>
            <p:column styleClass="ui-foreignKey-column" rendered="#{!cc.attrs.readOnly}">
                <p:commandButton id="btn" immediate="true" binding="#{cc.btn}" icon="ui-icon-search"                                 update="selectConstrDialog" 
                            oncomplete="PF('selectConstrListWidget#{cc.clientId}').show()"/>
            </p:column>
            <p:column styleClass="ui-foreignKey-column3">
                <h:panelGroup id="txt">
                    <h:outputText binding="#{cc.lbl}" escape="false" 
                            styleClass="ui-foreignKey-label" 
                            style="width:#{cc.attrs.labelWidth}"/>
                </h:panelGroup>
            </p:column>
        </p:row>
    </p:panelGrid>

<!-- Affichage uniquement du label -->
    <h:panelGroup rendered="#{cc.attrs.labelOnly}">
        <h:outputText value="#{cc.lbl.value}" escape="false"/>
    </h:panelGroup>

Nous avons vu que la classe spécialisée contenait deux objets, un UIInput pour gérer la saisie et afficher le code et un UIOutput pour afficher la décodification.

Pour associer ces objets à notre vue, l'astuce consiste à utiliser le binding. L'expression #{cc} représente le composant, nous avons donc accès à toutes les propriétés et toutes les méthodes du composant par elle.

 
Sélectionnez
<p:inputText id="key" binding="#{cc.key}".../> et <h:outputText binding="#{cc.lbl}".../>

Il est inutile à notre classe d'instancier quoi que ce soit, elle récupère les instances de la page.

Le champ « clé » permet de saisir directement un code constructeur, il faut donc pouvoir gérer une modification de sa valeur. C'est pourquoi lui est associée une action Ajax sur l'événement « change » rattachée à la méthode updateValue(…) de notre composant.

IV-F-2. Partie liste de sélection

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
<!-- Liste de sélection -->

    <p:dialog id="selectConstrDialog" widgetVar="selectConstrListWidget#{cc.clientId}"                             dynamic="true" rendered="#{cc.attrs.readOnly == false}" 
                    header="Selection d'un constructeur">
        <p:panelGrid>
            <p:row>
                <p:column>
                    <h:outputText value="Code"/>
                </p:column>
                <p:column>
                    <p:inputText value="#{foreignKeyConstrDataModel.filtreCode}" style="width:100px"/>
                </p:column>
                <p:column>
                    <h:outputText value="Nom"/>
                </p:column>
                <p:column>
                    <p:inputText value="#{foreignKeyConstrDataModel.filtreNom}" style="width:300px"/>
                </p:column>
            </p:row>

            <p:row>
                <p:column>
                    <h:outputText value="Code postal"/>
                </p:column>
                <p:column>
                    <p:inputText value="#{foreignKeyConstrDataModel.filtreCp}" style="width:100px"/>
                </p:column>
                <p:column>
                    <h:outputText value="Ville"/>
                </p:column>
                <p:column>
                    <p:inputText value="#{foreignKeyConstrDataModel.filtreVille}" style="width:300px"/>
                </p:column>
            </p:row>

            <p:row>
                <p:column colspan="2">
                    <p:commandButton value="Rechercher" action="#{foreignKeyConstrDataModel.loadData()}"                                 update="selectConstrList" />
                </p:column>
            </p:row>
        </p:panelGrid>


            <p:dataTable var="item" id="selectConstrList"
                             value="#{foreignKeyConstrDataModel.list}"
                            style="width:700px;padding-top:10px" 
                            rowKey="#{item.code}"
                            emptyMessage="Aucun enregistrement" 
                            paginator="true" 
                            rows="10"
                            selectionMode="single">

            <p:ajax event="rowSelect" listener="#{cc.updateValue}" 
                            update=":#{cc.clientId}:key :#{cc.clientId}:btn :#{cc.clientId}:txt" oncomplete="PF('selectConstrListWidget#{cc.clientId}').hide()"/>

            <p:column headerText="Code" width="80">
                <h:outputText value="#{item.code}"/>
            </p:column>

            <p:column headerText="Nom">
                <h:outputText value="#{item.nom}"/>
            </p:column>

            <p:column headerText="CP">
                <h:outputText value="#{item.cp}" width="100"/>
            </p:column>

            <p:column headerText="Ville">
                <h:outputText value="#{item.ville}" />
            </p:column>
        </p:dataTable>
    </p:dialog>

La liste de sélection sera affichée par une boite de dialogue chargée à la demande afin d'éviter d'alourdir inutilement la page utilisatrice et de ralentir le rendu global.

La première partie de la page représente les critères de sélection, suivie de la liste de sélection.

La gestion de cette liste est confiée à un ManagedBean standard nommé foreignKeyConstrDataModel et placé dans un scope ViewScoped.

Cette page ne contient rien de particulier excepté la méthode Ajax sur la sélection d'une ligne (event rowSelect) rattachée à la méthode de notre composant updateValue(…)

Dans notre exemple, à chaque fois que la liste de sélection est appelée, les critères de sélection sont réinitialisés.

Certains pourraient vouloir récupérer les précédentes valeurs, ça dépendra des besoins, et pour le faire, il suffira de placer les critères dans un ManagedBean de scope SessionScoped et d'utiliser une référence dans le ManagedBean foreignKeyConstrDataModel.

V. Conclusion et remerciements

Nous avons vu que la création de composants était plutôt simple et qu'il était facile de les utiliser partout où nécessaire dans les pages de l'application, de la même manière que toutes les bibliothèques de composants graphiques (Primefaces, Richfaces, etc.).

D'un point de vue plus général, il serait très intéressant de coder ces modules en dehors des projets « utilisateurs ». Il suffirait alors d'intégrer le jar des composants dans la partie /WEB-INF/lib du projet web pour les utiliser.

Il suffira de créer un projet Java et d'inclure dans son répertoire /META-INF notre répertoire « resources ».

Ce répertoire contiendra, en plus des pages xhtml de définition des composants, un fichier faces-config.xml pour les ManagedBean. Ceux-ci seront alors automatiquement inclus à votre application utilisatrice.

Si vous utilisez des fichiers de propriétés (.properties) pour vos messages ou constantes, il faudra passer par un ManagedBean spécifique à votre projet. En effet, le ClassLoader de l'application utilisatrice n'est pas connu, il faut donc gérer ces éléments par une ressource liée au ClassLoader des composants.

Ensuite, vous exporterez votre projet sous forme de jar et vous n'aurez plus qu'à inclure celui-ci là où vous en avez besoin.

Je tiens à remercier Mickael Baron pour la mise au gabarit et ClaudeLELOUP pour sa relecture avisée.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Olivier Butterlin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.