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
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 :
<
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 ». |
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
<?xml
version
=
'1.0'
encoding
=
'UTF-8'
?>
<!
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
:
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 »
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 :
2.
<
util
:
graphicBoolean
attribut1
=
"valeur1"
>
<
util
:
foreignKey-Constr
attribut1
=
"valeur1"
attribut2
=
"valeur2"
>
Les valeurs des attributs sont soit des constantes, soit des expressions
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 :
Scripts de création
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) :
Lorsque l'utilisateur cliquera sur le bouton, une liste de sélection multicritère sera affichée, son apparence sera la suivante :
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 :
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▲
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 :
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 :
- Lors de la sélection d'une ligne dans la liste de sélection ;
- 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
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.
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 :
- L'affichage de la clé, le bouton et la décodification ;
- La liste de sélection.
IV-F-1. Partie affichage▲
<!-- 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.
<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▲
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.