Just a simple site about seam and java, in french
icône RSS icône Emai icône Accueil
  • Création d’un composant facelet personnalisé avec Facelets JSF et Richfaces

    Posté le 12 février 2009 Mikael Robert 3 commentaires

    Exemple de création de composant facelets

    Si vous utilisez JSF, vous avez du 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éer.
    Bien heureusement il existe une solution alternative grâce à Facelets
    En effet vous pouvez créer un assemblage de composant JSF qui deviendra alors un composant en soit, mais un composant Facelet cette fois.
    Ce qui ne changera rien dans son utilisation.

    Le but ici va donc être de créer une list box qui peut recevoir n’importe quoi dans la liste déroulante,
    dans mon cas j’avais besoin de listes ou d’arbre de checkbox avec label.
    Donc d’utiliser dans ma liste déroulante, soit une rich:dataTable soit un rich:tree.

    On souhaite arriver à un composant générique qui permettrait de faire ce genre de chose :

    Le composant avec une rich:dataTable

    Le composant avec une rich:dataTable

    Le composant avec un rich:tree

    Le composant avec un rich:tree

    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 composants dans un fichier xhtml

    Configuration

    Le plus simple pour intégrer les composants que vous créer à votre WAR est de les placer dans le repertoire WEB-INF
    Par exemple j’ai créer un repertoire components dans WEB-INF,
    Voyons maintenant la configuration du composant, tout d’abord dans web.xml :
    On indique à Facelets ou alors 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>

    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 :

    <?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>

    Création du composant

    On peut maintenant créer le composant :
    J’ai laissé la partie CSS/Javascript volontairement au cas ou vous souhaitiez réutiliser ce composant.
    Tout ce qui correspond au position et au z-index est néanmoins important pour permettre la création de la pseudo liste dédroulatne.
    Le javascript (ici fait avec jQuery) est utilisé pour enrouler/dérouler la liste, et pour compter les checkbox comptés dans le contenu.
    Il s’agit d’un exemple de comportement javascript que l’on peut attribuer à un composant, à vous d’adadapter à 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 avec ce que vous voulez le comportement de votre composant.
    Voir même 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 id à partir de l’id du composant crée dans la JSF,
    pour éviter les erreurs de duplicate ID. Donc on a des id avec des EL la dedans, 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 inserez 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'ai 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 deroulante 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 deroulante */
                            jQuery("##{id}magicBoxList").hide();
     
     
                            /* Si le parametre footer est a faux on cache le footer*/
                            if (!jQuery(#{footer})) {
                                jQuery("##{id}footerListBox").hide();
                            }
    						/* On remplis la valeur du champ input par defaut */
                            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 fleche 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();
                                  }
                            });
     
     
                            /* Deselectionne 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és à 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 immite une select box avec un inputText desactivé et une image de fleche -->
                        <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 deroulante-->
                      <div class="fullListDropDownCheckBox" id="#{id}magicBoxList">
                      <div class="dropDownSelectListCheckBox" id="#{id}dropListContainer">
                      <!-- Le contenu de la liste deroulante sera inséré ici-->
                        <ui:insert />
                      </div>
                      <!-- On ajoute un footer a la liste déroulante avec un lien  "tout deselectionner"-->
                      <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éer le composant comme on ferait une page jsf classique, la différence réside uniquement dans le fait
    qu’on créer une composition.

    L’élément important ici est le
    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és 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érable sous forme d’el
    avec la même casse 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é à des appels javascript
    (déclarés dans la partie javascript) mais il y a d’autres façon de faire.

    Utilisation du composant

    Et voici maintenant un exemple d’utilisation qui permet d’arriver aux captures au 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 checkbox, 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.
    N’hésitez pas à commenter si vous avez des questions ou des remarques constructives.

     

    3 responses to “Création d’un composant facelet personnalisé avec Facelets JSF et Richfaces” icône RSS

    • Interessante Informationen.

    • Bonjour, de mon côté je fait à peu prèt la même chose mais avec un arbre de cases à cocher (quand le père est coché tous les fils le sont, etc …). Ca marche bien en passant par le serveur, je change les valeurs dans les bean java ratachés aux cases à cocher (c’est un peu lourd). Je n’ai pas (encore) trouvé la soluce pour implémenté ce comportement côté client : impossible de mettre des ids dynamiques sur les , c’est donc difficile de manipuler leur valeur en js …
      Si tu as une idée, je preneuse.

    • Bah je le fais avec jQuery dans mon exemple, tu peux utiliser les parametres passés à ton composant pour mettre des el dans tes IDs.


    Laisser une réponse