I. Introduction▲
Le but de ce tutoriel est de créer une list box qui peut recevoir n'importe quoi dans la liste déroulante, dans mon cas j'ai besoin de listes ou d'arbres de checkbox avec label.
J'ai donc besoin d'utiliser dans ma liste déroulante, soit une rich:dataTable soit un rich:tree.
Je vous renvoie au Tutoriel de L. Mellouk sur richfacesTutoriel de L. Mellouk sur richfaces pour l'utilisation du rich:tree ou de la rich:dataTable.
On souhaite arriver à un composant générique qui permettrait de faire ce genre de choses :
II. Les étapes▲
Créer un composant Facelets requiert trois étapes :
- création d'un fichier de taglib pour pouvoir le déclarer à facelets et l'utiliser ;
- déclaration du fichier de taglib à facelets dans le web.xml ;
- création du composant dans un fichier xhtml.
III. Configuration▲
Le plus simple pour intégrer les composants que vous créez à votre WAR est de les placer dans le répertoire WEB-INF.
Par exemple j'ai créé un répertoire components dans WEB-INF.
Voyons maintenant la configuration du composant, tout d'abord dans web.xml :
on indique à Facelets d'aller chercher les composants additionnels dans WEB-INF/facescomponents.taglib.xml :
<context-param>
<param-name>
facelets.LIBRARIES</param-name>
<param-value>
/WEB-INF/facescomponents.taglib.xml</param-value>
</context-param>
IV. Taglib▲
Ensuite, comme vous pouvez le voir précédemment, on crée un fichier taglib pour notre composant : voici le fichier facescomponents.taglib.xml :
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"facelet-taglib_1_0.dtd"
>
<facelet-taglib>
<namespace>
http://www.dreamisle.net/facescomponents</namespace>
<tag>
<tag-name>
selectListCheckBox</tag-name>
<source>
components/selectListCheckBox.xhtml</source>
</tag>
</facelet-taglib>
V. Création du composant▲
On peut maintenant créer le composant.
J'ai fourni CSS/JavaScript volontairement au cas où vous souhaitiez réutiliser ce composant.
Néanmoins, tout ce qui correspond à la position et au z-index est important pour permettre la création de la pseudo liste déroulante.
Le JavaScript (ici fait avec jQuery) est utilisé pour enrouler/dérouler la liste, et pour compter les checkbox cochées dans le contenu.
Il s'agit d'un exemple de comportement JavaScript que l'on peut attribuer à un composant, à vous d'adapter à votre choix. J'ai choisi d'utiliser jQuery pour plus de portabilité.
De plus la lbibliothèque permet d'éviter de polluer le code jsf d'appels JavaScript sur les “onclick”, “onchange”, etc.
Vous pouvez bien sûr définir ce que vous voulez dans le comportement de votre composant, voire contrôler le composant avec un objet Seam appelé en ajax, mais cela le rend inutilisable en dehors de votre contexte Seam.
Le système d'id est un peu compliqué, en fait, on crée nos id à partir de l'id du composant créé dans la JSF, pour éviter les erreurs de duplicate ID.
Donc les id sont faits avec des EL, mais ne vous formalisez pas de cela.
Cela complexifie la lecture du code jQuery et JSF, mais c'est une façon simple d'éviter le duplicate ID si vous insérez plusieurs fois le composant dans la même page.
<!DOCTYPE composition PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
ui
:
composition
xmlns
=
"http://www.w3.org/1999/xhtml"
xmlns
:
s
=
"http://jboss.com/products/seam/taglib"
xmlns
:
ui
=
"http://java.sun.com/jsf/facelets"
xmlns
:
f
=
"http://java.sun.com/jsf/core"
xmlns
:
h
=
"http://java.sun.com/jsf/html"
xmlns
:
rich
=
"http://richfaces.org/rich"
xmlns
:
a4j
=
"http://richfaces.org/a4j"
>
<!-- Style-->
<style>
/* on met le composant en position relative, pour pouvoir placer la liste déroulante au-dessus en absolute après*/
.divLayoutSelectListCheckBox {
position: relative;
}
/* la largeur totale du composant*/
.dataGridLayoutSelectListCheckBox {
width:350px
}
/* le champ input simulant une select box */
.idropDownSelectListCheckBoxInput {
width:332px;
margin:0px;
z-index: 2;
}
/* uniquement pour que la rich:dataTable n'ait pas de bordures. */
.dataTableDropDownList {
width: 350px;
border-style: none;
background-color: #EEF5FE;
}
/* uniquement pour que les colonnes de la rich:dataTable n'aient pas de bordures. */
.dataTableDropDownListColumn {
border-bottom: 0px;
border-right: 0px;
}
/* le div de la liste en elle-même. */
.dropDownSelectListCheckBox {
height: 202px;
overflow: auto;
background-color: #EEF5FE;
border: 1px solid #CAC5BE;
}
/* on fixe en position absolue et avec un z-index important le div entourant la liste déroulante pour qu'elle passe au-dessus du reste*/
.fullListDropDownCheckBox {
position: absolute;
z-index: 5;
}
/* le pied de la liste déroulante*/
.dropDownSelectListCheckBoxFooter {
background-color: #BED6F8;
border: 1px solid #CAC5BE;
}
</style>
<!-- Javascript for the component behaviour-->
<
a4j
:
loadScript
src
=
"resource://jquery.js"
/>
<script
type
=
"text/javascript"
>
jQuery(document).ready(function() {
/* par défaut on cache la liste déroulante */
jQuery("##{id}magicBoxList").hide();
/* si le paramètre footer est à faux on cache le footer*/
if (!jQuery(#{footer})) {
jQuery("##{id}footerListBox").hide();
}
/* on remplit la valeur du champ input par défaut */
jQuery("##{formId}\\:#{id}statesinput").attr("value", "0" + " " + '#{selectedLabel}');
/* un clic dans l'input cache ou montre la liste*/
jQuery("##{formId}\\:#{id}statesinput").click(function() {
if (jQuery("##{id}magicBoxList").is(":hidden")) {
jQuery("##{id}magicBoxList").show();
} else {
jQuery("##{id}magicBoxList").hide();
}
});
/* un clic sur la flèche cache ou montre la liste.*/
jQuery("##{formId}\\:#{id}showandhidelinkid").click(function() {
if (jQuery("##{id}magicBoxList").is(":hidden")) {
jQuery("##{id}magicBoxList").show();
} else {
jQuery("##{id}magicBoxList").hide();
}
});
/* désélectionne toutes les checkboxes lorsqu'on clique sur le lien correspondant*/
jQuery('##{formId}\\:#{id}deselectAllButtonFooter').click(function(){
jQuery("##{id}dropListContainer input[@type='checkbox']").attr('checked', false);
jQuery("##{formId}\\:#{id}statesinput").attr("value", "0 " + '#{selectedLabel}');
});
/* met à jour le compteur de cases cochées à chaque clic sur une checkbox */
jQuery("##{id}dropListContainer input[@type='checkbox']").click(function() {
count = jQuery("##{id}dropListContainer input[@type='checkbox']:checked").length;
if (count > #{maxCheck}) {
jQuery("##{formId}\\:#{id}statesinput").attr("value", "#{maxSelectMessage}" + " " + "#{maxCheck}" + " éléments");
jQuery(this).attr('checked', false);
} else {
jQuery("##{formId}\\:#{id}statesinput").attr("value", count + " " + '#{selectedLabel}');
}
});
});
</script>
<!-- le code proprement dit du composant -->
<
h
:
panelGrid
columns
=
"2"
>
<
s
:
div
styleClass
=
"divLayoutSelectListCheckBox"
id
=
"#{id}idPanelGridForFc"
>
<
h
:
panelGrid
columns
=
"2"
border
=
"0"
cellpadding
=
"0"
cellspacing
=
"0"
styleClass
=
"dataGridLayoutSelectListCheckBox"
>
<!-- on imite une select box avec un inputText désactivé et une image de flèche -->
<
h
:
inputText
id
=
"#{id}statesinput"
readonly
=
"true"
styleClass
=
"idropDownSelectListCheckBoxInput"
/>
<
a4j
:
commandLink
id
=
"#{id}showandhidelinkid"
>
<
h
:
graphicImage
value
=
"/img/arrow.png"
alt
=
">"
/>
</
a4j
:
commandLink>
</
h
:
panelGrid>
<
h
:
panelGroup>
<!-- la liste déroulante-->
<div
class
=
"fullListDropDownCheckBox"
id
=
"#{id}magicBoxList"
>
<div
class
=
"dropDownSelectListCheckBox"
id
=
"#{id}dropListContainer"
>
<!-- le contenu de la liste déroulante sera inséré ici-->
<
ui
:
insert />
</div>
<!-- on ajoute un footer à la liste déroulante avec un lien "tout déselectionner"-->
<div
class
=
"dropDownSelectListCheckBoxFooter"
id
=
"#{id}footerListBox"
>
<
a4j
:
commandLink
id
=
"#{id}deselectAllButtonFooter"
>
Tout désélectionner
</
a4j
:
commandLink>
</div>
</div>
</
h
:
panelGroup>
</
s
:
div>
</
h
:
panelGrid>
</
ui
:
composition>
Comme vous pouvez le voir, on a créé le composant de la même manière que l'on écrirait une page jsf classique, la différence réside uniquement dans le fait que l'on crée une composition.
L'élément important ici est le <ui:insert/>
C'est ici que sera placé le contenu de la liste déroulante, remplie par l'utilisateur du composant dans une page JSF.
Autre élément à noter : les EL utilisées dans le composant.
Par exemple ici #{id} : il s'agit en fait de récupérer les paramètres, passés au composant de manière classique dans la JSF.
Pour mieux comprendre lisez la partie “utilisation du composant” vous allez tout de suite voir que les paramètres déclarés au composant, sont récupérables sous forme d'EL avec la même casse que dans l'EL, ainsi votre composant est facilement paramétrable.
#{formId}, #{maxSelectMessage}, #{maxCheck}, #{footer} et #{selectedLabel} ne sont utilisés que dans les fonctions de comportement jQuery du composant.
Vous voyez que vous pouvez même utiliser les paramètres pour définir le comportement du composant avec JavaScript.
J'ai choisi pour l'exemple d'utiliser des a4j:commandLink liés à des appels JavaScript (déclarés dans la partie JavaScript), mais il y a bien d'autres façons de faire.
VI. Utilisation du composant▲
Et voici maintenant un exemple d'utilisation qui permet d'arriver aux captures du début de cet article :
<!DOCTYPE composition PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
ui
:
composition
xmlns
=
"http://www.w3.org/1999/xhtml"
xmlns
:
s
=
"http://jboss.com/products/seam/taglib"
xmlns
:
ui
=
"http://java.sun.com/jsf/facelets"
xmlns
:
f
=
"http://java.sun.com/jsf/core"
xmlns
:
h
=
"http://java.sun.com/jsf/html"
xmlns
:
rich
=
"http://richfaces.org/rich"
xmlns
:
a4j
=
"http://richfaces.org/a4j"
xmlns
:
fc
=
"http://www.meteojob.com/facescomponents"
template
=
"layout/template.xhtml"
>
<
ui
:
define
name
=
"body"
>
<
rich
:
panel>
<
f
:
facet
name
=
"header"
>
select test </
f
:
facet>
<
h
:
form
id
=
"formRichListId"
>
<!-- ici une liste avec en label une chaine de caracteres, et devant le label une checkbox -->
<
fc
:
selectListCheckBox
id
=
"fcSelectList"
formId
=
"formRichListId"
selectedLabel
=
"éléments choisis"
maxCheck
=
"4"
maxSelectMessage
=
"Vous devez choisir au maximum:"
footer
=
"true"
>
<
rich
:
dataTable
value
=
"#{dreamSelectBox.theList}"
var
=
"item"
styleClass
=
"dataTableDropDownList"
>
<
rich
:
column
styleClass
=
"dataTableDropDownListColumn"
>
<
h
:
selectBooleanCheckbox
value
=
"false"
name
=
"checkboxRichList"
/>
</
rich
:
column>
<
rich
:
column
styleClass
=
"dataTableDropDownListColumn"
>
<
h
:
outputText
value
=
"#{item}"
escape
=
"false"
/>
</
rich
:
column>
</
rich
:
dataTable>
</
fc
:
selectListCheckBox>
<
rich
:
spacer
height
=
"50"
/>
<!-- ici un arbre avec aussi des checkboxes, j'ai
repris l'arbre donné en exemple dans la démo en ligne de richfaces -->
<
fc
:
selectListCheckBox
id
=
"fcSelectTree"
formId
=
"formRichListId"
selectedLabel
=
"éléments cochés"
maxCheck
=
"4"
maxSelectMessage
=
"Vous devez choisir au maximum:"
footer
=
"true"
>
<
rich
:
tree
style
=
"width:350px;"
nodeSelectListener
=
"#{dreamSelectBox.theTree.processSelection}"
reRender
=
"selectedNode"
ajaxSubmitSelection
=
"true"
switchType
=
"client"
value
=
"#{dreamSelectBox.theTree.getTreeNode()}"
var
=
"item"
ajaxKeys
=
"#{null}"
>
<
rich
:
treeNode>
<
f
:
facet
name
=
"iconLeaf"
><
h
:
selectBooleanCheckbox
value
=
"false"
/></
f
:
facet>
<
h
:
outputText
value
=
"#{item}"
/>
</
rich
:
treeNode>
</
rich
:
tree>
</
fc
:
selectListCheckBox>
</
h
:
form>
</
rich
:
panel>
</
ui
:
define>
</
ui
:
composition>
Et voilà, vous avez votre composant, j'espère que cet article aura été utile.