Création d'un composant facelet personnalisé avec Facelets JSF et Richfaces

Si vous utilisez JSF, vous avez dû remarquer qu'on fait souvent des assemblages similaires de composants.

Parfois l'idée vous passe par la tête de créer votre propre composant JSF pour remplacer ces assemblages redondants, mais créer un composant JSF est long et difficile.
La difficulté réside notamment dans la gestion du cycle de vie JSF du composant que vous créez.
Bien heureusement il existe une solution alternative grâce à Facelets
En effet vous pouvez créer un assemblage de composants JSF qui deviendra alors un composant en soit, mais un composant Facelet cette fois.
Ce qui ne changera en rien son utilisation.

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Le but de ce tutoriel est de 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 :
Le composant avec une rich:dataTable
Le composant avec un rich:tree

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 repertoire WEB-INF
Par exemple j'ai crée un répertoire components dans WEB-INF.
Voyons maintenant la configuration du composant, tout d'abord dans web.xml :
On indique à Facelets d'allerchercher les composants additionnels dans WEB-INF/facescomponents.taglib.xml :

Ajout dans web.xml
Sélectionnez

<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éer un fichier taglib pour notre composants :
voici le fichier facescomponents.taglib.xml :

facescomponents.taglib.xml
Sélectionnez

<?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 ou 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 librairie permet d'éviter de polluer le code jsf d'appels javascript sur les “onclick”, “onchange” etc …

Vous pouvez bien sur définir ce que vous voulez dans le comportement de votre composant, Voire même contrôler le composant avec un objet Seam appellé 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éer nos ids à partir de l'id du composant créé dans la JSF, pour éviter les erreurs de duplicate ID.
Donc les ids 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.

le composant en lui meme : selectListCheckBox.xhtml
Sélectionnez

<!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'aie 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 meme. */			
            .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 deroulante*/			
            .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 click 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 click 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 checkbox 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 click 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 desactivé 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="&gt;"/>
                    </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écuperer 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 utilisez 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 javascripts (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 :

 
Sélectionnez

<!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 checkboxs, 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.

Liste de mes articles sur Seam :
Architecture Maven d'un projet Seam 2
Annotations Seam : Datamodel, Factory et Unwrap.
Création d'un composant facelet personnalisé avec Facelets JSF et Richfaces.
Intégration d'Hibernate Search à une application Seam 2.
Présentation Globale de Seam.
  

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 © 2009 Mikael Robert. 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.