Champ de saisie "autocomplete" en JSF

Les champs de saisie intelligents
proposant instantanément des suggestions de valeurs dès la saisie des premières lettres d'un mot sont de plus en plus répandus et offrent une expérience très appréciée des utilisateurs.
Je vous propose de suivre dans cet article une introduction à la mise en oeuvre de ce champ de saisie en JSF avec l'aide de la librairie PrimeFaces et de son composant autoComplete
.
Nous découvrirons que l'intégration d'une librairie de composants JSF prêts à l'emploi
telle que PrimesFaces, RichFaces ou encore ICEFaces, facilite grandement le travail du développeur dans la réalisation de pages web dynamiques basées sur la technologie AJAX.
La plateforme technique utilisée pour rédiger cet article est constituée du JDK version 1.6, du serveur GlassFish version 3.1.2 qui inclut la librairie d'implémentation JSF Mojarra version 2.1.6 et de la librairie PrimeFaces version 3.4
Contexte d'utilisation d'un champ autocomplete

autocomplete
Les champs de type autocomplete
sont particulièrement adaptés à la saisie de données limitées à un sous-ensemble de valeurs pré-définies ou encore à la saisie de données de recherche.
En effet, dans le premier cas, la saisie est orientée
en fournissant à l'utilisateur une liste de valeurs autorisées se rapprochant des premières lettres qu'il a saisies.
Ce cas correspond par exemple à la saisie du nom d'une ville, d'un client ou encore d'un article à commander.
Dans le cas d'une recherche, l'approche est un peu différente puisque l'utilisateur se voit suggérer une liste de valeurs relative à son début de saisie, sans pour autant le contraindre à suivre les propositions qui lui sont faîtes.
Le cas du champ de recherche de Google est un bon exemple : des mots clés sont suggérés en fonction de multiples critères (localisation géographique de l'internaute, fréquence des mots clés demandés, etc...) mais l'utilisateur reste finalement libre de saisir les mots clé de son choix et de lancer la recherche selon ses propres critères.
A noter que de manière générale, les données à saisir dans un champ autocomplete
ne doivent pas être codifiées.
Elles doivent représenter des informations qui ont du sens pour l'utilisateur et être adaptées à son langage naturel.
Autrement, il est préférable pour la saisie de valeurs codifiées (par exemple un code client), d'adjoindre au champ de saisie un accès à une liste des codes incluant des fonctionnalités de recherche sur des critères autres que des valeurs codifiées (nom, adresse...).
Le composant autoComplete
de la librairie RichFaces
La librairie RichFaces inclut le composant nommé autoComplete
à la fois très ouvert en termes de personnalisation et très facile à intégrer dans une page JSF.
Une démonstration de ce composant est disponible sur le site officiel www.primefaces.org.
Son implémentation la plus simple consiste à :
- Déclarer le composant Autocomplete dans la page JSF à l'aide de la balise
<p:autoComplete />
,Code 1 : exemple de déclaration du composant autoComplete PrimeFaces dans une vue Facelet.<?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:p="http://primefaces.org/ui"> <h:head> <title>Démonstration AutoComplete PrimeFaces</title> </h:head> <h:body> <h1>Recherche d'un Client</h1> <h:form id="form"> <h:outputLabel value="Nom du client : " for="clientName" /> <p:autoComplete id="clientName" value="#{autoCompleteClientBean.selectedClient}" completeMethod="#{autoCompleteClientBean.complete}" /> </h:form> </h:body> </html>
- Créer un managed bean associé disposant à minima :
- D'une propriété contenant la valeur saisie dans le champ,
- D'une méthode chargée de fournir les suggestions de valeurs au composant à partir du texte saisi dans le champ.
Code 2 : exemple de managed bean associé au composant autoComplete PrimeFaces.package managedbeans; import java.util.ArrayList; import java.util.List; import javax.faces.bean.ManagedBean; @ManagedBean public class AutoCompleteClientBean { private String selectedClient; public List<String> complete(String query) { List<String> results = new ArrayList<String>(); for (int i = 0; i < 10; i++) { results.add(query + i); } return results; } public String getSelectedClient() { return selectedClient; } public void setSelectedClient(String selectedClient) { this.selectedClient = selectedClient; } }
Une autre implémentation possible consiste à relier directement le composant autoComplete aux objets métier de l'application exposés sous forme de POJO, par l'intermédiaire d'un Convertisseur personnalisé, chargé de convertir le texte saisi en objet et inversement.
Dans le paragraphe suivant, nous nous focaliserons sur l'implémentation d'un champ autoComplete dont les suggestions seront proposées à partir d'une liste d'objets.
L'application de démonstration demo_autocomplete
Les fonctionnalités à développer
L'application web de démonstration que je vous propose de développer permet de consulter les informations détaillées d'un client dont le nom est saisi au préalable dans un champ autocomplete.
L'utilisateur dispose évidemment d'une assistance à la saisie; il se voit proposer une liste de clients correspondant aux premières lettres qu'il a entrées.
A la validation du formulaire, l'utilisateur est dirigé vers une page affichant les informations détaillées du client sélectionné.

demo_autocomplete
Les clients sont structurés dans l'application sous forme de POJO créés statiquement pour l'exemple.
Ces POJO pourraient dans une application complète, correspondre à des entités de type JPA.

demo_autocompletesous NetBeans
Le code source de cette application de démonstration comprend les fichiers suivants :
- web.xml : descripteur de déploiement de l'application de démonstration. Son contenu généré en standard est suffisant pour les besoins de la démonstration.
- glassfish-web.xml : descripteur de déploiement spécifique à GlassFish dans lequel est précisé le jeu de caractères UTF-8 pour les besoins de PrimeFaces à l'encodage des vues.
- index.xhtml : vue Facelet d'affichage d'un champ de saisie et d'un bouton de validation du client renseigné.
- result.xhtml : vue Facelet d'affichage des informations du client sélectionné à la page index.xhtml.
- AutoCompleteClientBean.java : managed bean connecté au champ de saisie
autoComplete
disposant d'une propriété pour mémoriser le client sélectionné, d'une méthode fournissant les suggestions de noms de clients et enfin, d'une méthode appelée à la validation du formulaire (action du bouton Submit). - Client.java : POJO décrivant les propriétés d'un client.
- ClientModel.java : classe d'accès aux données des clients .
- ClientConverter.java : convertisseur personnalisé chargé de transtyper les données objets du client en données textes affichables dans la vue Facelet.
L'application a été générée avec NetBeans à partir du menu File | New Project...
en renseignant les étapes suivantes :
- Etape Choose Project : Categories:
Java Web
Projects:Web Application
, - Etape Name and Location : Project Name:
demo_autocomplete
, - Etape Server and Settings : Server:
GlassFish Server 3+
Java EE Version:Java EE 6 Web
, - Etape Frameworks : Framework:
JavaServer Faces
Libraries:JSF 2.1
Components:PrimeFaces
.
La vue index.xhtml
Il s'agit de la vue Facelet affichée par défaut.
Elle affiche un formulaire <h:form/>
incluant une instance du composant <p:autoComplete/>
de saisie du nom du client recherché et un bouton d'action <h:commandButton/>
pour valider le client sélectionné.
<?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:p="http://primefaces.org/ui"> <h:head> <title>PrimeFaces AutoComplete Demonstration</title> </h:head> <h:body> <h1>Client search...</h1> <p>Enter the Client's name for displaying his informations.</p> <p>For example : Renault, Singer, Canon, Orange, Lyonnaise des eaux...</p> <h:form id="form"> <h:outputLabel value="Client's name : " for="clientName" /> <p:autoComplete id="clientName" value="#{autoCompleteClientBean.selectedClient}" completeMethod="#{autoCompleteClientBean.complete}" converter="clientConverter" var="c" itemLabel="#{c.name}" itemValue="#{c}" forceSelection="true" required="true"/> <h:commandButton value="Submit" action="#{autoCompleteClientBean.submit}"/> </h:form> </h:body> </html>
demo_autocomplete.
Arrêtons-nous en particulier sur les attributs renseignés pour le composant autoComplete :
- value=
"#{autoCompleteClientBean.selectedClient}"
: expression EL qui référence l'objet selectedClient du managed bean AutoCompleteClientBean. En effet, l'échange de données entre la vue et le managed bean est assuré par un objet de type Client dont sa transformation texte / objet est assurée par le convertisseur indiqué pour l'attribut converter. - completeMethod=
"#{autoCompleteClientBean.complete"
: il indique la méthode du managed bean à invoquer pour obtenir la liste des suggestions à partir des lettres saisies dans le composant. - converter=
"clientConverter"
identifiant du convertisseur appelé pour disposer de l'objet de type Client correspondant au client sélectionné dans le composant. - var=
"c"
nom donné à l'itérateur utilisé pour parcourir les suggestions basées sur les objets de type Client. Ce nom est utilisé par les attributs itemLabel et itemValue pour désigner l'objet Client courant de l'itérateur. - itemLabel=
"#{c.name}"
libellé de l'objet Client qui est affiché pour chaque suggestion dans la liste déroulante. - itemValue=
"#{c}"
valeur de la suggestion, en l'occurence l'objet Class correspondant à la suggestion. - forceSelection=
"true"
oblige l'utilisateur à sélectionner un client parmi les suggestions proposées, autrement vide le champ à la perte du focus.
La vue result.xhtml
Elle a pour seule fonction d'afficher les informations du client sélectionné dans la vue index.xhtml.
Les informations du client sont obtenues à partir de l'objet Client contenu dans la propriété selectedClient
du managed bean.
Cette vue est appelée par la méthode submit()
du managed bean.
<?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"> <h:head> <title>Selected Client...</title> </h:head> <h:body> <h1>Selected Client...</h1> <p> <br/>Code: <strong>#{autoCompleteClientBean.selectedClient.clientCode}</strong> <br/>Name: <strong>#{autoCompleteClientBean.selectedClient.name}</strong> <br/>Address: <strong>#{autoCompleteClientBean.selectedClient.address}</strong> <br/>City: <strong>#{autoCompleteClientBean.selectedClient.city}</strong> </p> <p>Back to the <h:link outcome="index?faces-redirect=true">main page</h:link></p> </h:body> </html>
demo_autocomplete.
Le managed bean AutoCompleteClientBean.java
Il fournit au composant autoComplete via un appel de la méthode List<Client> complete(String query)
, la liste des suggestions pour la chaîne de caractères saisie. La recherche des clients à retourner en guise de suggestions est déléguée à la classe ClientModel
.
Il mémorise dans la propriété selectedClient
l'objet Client sélectionné à partir du composant autoComplete.
Enfin, le managed bean dispose de la méthode submit()
invoquée en réponse à l'action du bouton Submit et chargée simplement d'appeler la vue result.xhtml.
package managedbeans; import entities.Client; import java.util.List; import javax.faces.bean.ManagedBean; import model.ClientModel; @ManagedBean public class AutoCompleteClientBean { private Client selectedClient; public List<Client> complete(String query) { return ClientModel.getClientsByName(query); } public Client getSelectedClient() { return selectedClient; } public void setSelectedClient(Client selectedClient) { this.selectedClient = selectedClient; } public String submit() { System.out.println("Selected client: " + this.selectedClient.getName()); return "result"; } }
demo_autocomplete.
Le convertisseur ConvertisseurClient.java
Ce convertisseur personnalisé implémente l'interface javax.faces.convert.Converter
et ses deux signatures de méthode suivantes :
- String getAsString(FacesContext context, UIComponent component, Object value) : cette méthode est appelée à la construction de la liste des suggestions pour disposer du code client au format texte pour chaque objet Client retourné par la méthode
AutoCompleteClientBean.complete(String query)
. - Object getAsObject(FacesContext context, UIComponent component, String value) : elle est appelée après validation du formulaire de la vue index.xhtml (clic du bouton Submit). Elle permet d'obtenir l'objet Client à partir du code au format texte du client sélectionné dans le composant et ainsi, d'initialiser la propriété
selectedClient
du managed beanAutoCompleteClientBean
.
package converters; import entities.Client; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.FacesConverter; import model.ClientModel; @FacesConverter("clientConverter") public class ClientConverter implements Converter { @Override public Object getAsObject(FacesContext context, UIComponent component, String value) { return ClientModel.getClient(value); } @Override public String getAsString(FacesContext context, UIComponent component, Object value) { if (value == null || value.equals("")) { return ""; } else { return String.valueOf(((Client) value).getClientCode()); } } }
L'annotation @FacesConverter("clientConverter")
permet de déclarer le convertisseur dans le conteneur GlassFish et de lui attribuer l'identifiant clientConverter
référencé par le composant autoComplete à travers son attribut converter.
Le POJO Client.java
POJO utilisé pour transmettre à la vue les informations du client.
package entities; public class Client { private String clientCode; private String name; private String address; private String city; public Client(String clientCode, String name, String address, String city) { this.clientCode = clientCode; this.name = name; this.address = address; this.city = city; } public String getClientCode() { return clientCode; } public void setClientCode(String codeClient) { this.clientCode = codeClient; } public String getName() { return name; } public void setName(String nom) { this.name = nom; } public String getAddress() { return address; } public void setAddress(String adresse) { this.address = adresse; } public String getCity() { return city; } public void setCity(String ville) { this.city = ville; } }
demo_autocomplete.
La classe ClientModel.java
d'accès aux objets Client
Cette classe contient la liste statique static final ArrayList<Client> clients
des clients nécessaires à la démonstration.
D'autre part, elle dispose de la méthode Client getClient(String clientCode)
appelée par le convertisseur pour obtenir l'objet Client correspondant à un code client donné.
Enfin, elle comprend la méthode List<Client> getClientsByName(String pieceOfName)
invoquée par la méthode complete(String query)
du managed bean pour obtenir la liste des objets Client dont le nom commence par les lettres indiquées en paramètre.
A noter que l'identification des clients ne tient pas compte de la casse des caractères. Les chaînes texte à comparer sont au préalable transformées en minuscules (utilisation de la méthode String.toLowerCase()
), ce qui permet de retrouver les clients commençant par une lettre majuscule alors que la première lettre est saisie en minuscule.
package model; import entities.Client; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ClientModel { private static final ArrayList<Client> clients = new ArrayList<Client>(Arrays.asList( new Client("CLI001", "Renault","16 allée des Peupliers","Tourcoing"), new Client("CLI002", "Singer","87 avenue des Platanes","Paris"), new Client("CLI003", "Canon","125 rue des Fraises","Marseille"), new Client("CLI004", "Orange","36 allée des Templiers","Orleans"), new Client("CLI005", "SFR","43 impasse du Baron","Nantes"), new Client("CLI006", "Lyonnaise des eaux","58 tour des Cieux","Montpellier"), new Client("CLI007", "Veolia","63 rue des Allouettes","Avignon"), new Client("CLI008", "Avensys","17 allée des Pigeons","Toulouse"), new Client("CLI009", "Cultura","2 avenue des Amandiers","Angers"), new Client("CLI010", "Histoire d'or","55 tour des Moulins","Mulhouse"), new Client("CLI011", "Cap Gemini","654 avenue du Fond","Perpignan"), new Client("CLI012", "Free","39 rue des oiseaux","Niort"), new Client("CLI013", "Citroën","43 impasse des Marins","Roubais"), new Client("CLI014", "HP","24 tour des Vins","Strasbourg"), new Client("CLI015", "Yamaha","69 chemin des Vaisseliers","Limoges"), new Client("CLI016", "Royal Post","245 allée des Platanes","Nice"), new Client("CLI017", "Royal canin","25 tour des Parfums","Aix en Provence"), new Client("CLI018", "Rivoire & Carré","67 rue des Fruits","Orange"), new Client("CLI019", "Hays","18 allée des Arênes","Nîmes"), new Client("CLI020", "Total","11 passage Wilson","Evry"), new Client("CLI021", "Crédit Agricole","20 allée Jean-Moulin","Dijon"), new Client("CLI022", "Vinci","369 avenue Descartes","Nancy"), new Client("CLI023", "Bouygues","77 tour de Lyon","Bordeaux"), new Client("CLI024", "Alstom","66 passage sans issue","Toulouse"), new Client("CLI025", "Sanofi Aventis","55 chemin Paul Valery","Poitiers") )); public static Client getClient(String clientCode) { for (Client client : clients) { if (client.getClientCode().equals(clientCode)) { return client; } } return null; } public static List<Client> getClientsByName(String pieceOfName) { List<Client> clientsByName = new ArrayList<Client>(); for(Client client : clients) { if(client.getName().toLowerCase().startsWith(pieceOfName.toLowerCase())) clientsByName.add(client); } return clientsByName; } }
demo_autocomplete.
Le descripteur glassfish-web.xml
Si comme moi, vous utilisez GlassFish pour déployer cette application de démonstration, vous devrez ajouter à votre projet le descripteur de déploiement glassfish-web.xml spécifique à GlassFish et le completer comme illustré ci-après pour ne plus avoir l'alerte PWC4011: Unable to set request character encoding to UTF-8 from context...
dans le journal système du serveur.
En effet il semblerait, bien que JSF utilise habituellement UTF-8 pour restituer les vues, que le framework PrimeFaces demande au conteneur web de lui fournir le jeu de caractères à utiliser pour construire la vue à restituer. Or GlassFish lui indiquerait le jeu de caractères configuré par défaut, à savoir ISO-8859-1 et non pas UTF-8 !
Ce problème est notamment décrit à l'adresse https://code.google.com/p/primefaces/issues/detail?id=1195.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"> <glassfish-web-app error-url=""> <parameter-encoding default-charset="UTF-8"/> </glassfish-web-app>
demo_autocomplete.
Autres possibilités du composant autoComplete
Le composant autoComplete de la librairie PrimeFaces intègre des fonctionnalités qui n'ont pas été illustrées dans l'application de démonstration.
Parmi elles, j'évoquerai en particulier les fonctionnalités suivantes :
- Réglages du comportement.
Les attributs ci-dessous méritent d'être pris en considération, notamment pour des questions de performance :- Délai avant requête (
queryDelay
): il s'agit du délai écoulé entre la frappe d'un caractère et l'émission de la requête AJAX. Par défaut, cette durée est fixée à 300 ms. Elle peut être étendue par exemple à 1 s pour réduire le nombre de requêtes AJAX émises vers le serveur web. - Nombre de caractères avant requête (
minQueryLength
) : nombre minimum de caractères que doit saisir l'utilisateur pour que le composant autocomplete propose des suggestions. Par défaut, les suggestions sont proposées dès la saisie du premier caractère. - Nombre de suggestions (
maxResults
) : nombre maximum de suggestions proposées. Par défaut, toutes les suggestions correspondant aux premiers caractères saisis sont affichées.
- Délai avant requête (
- Affichage personnalisé des suggestions (balise
<p:column/>
) : les suggestions peuvent être présentées à l'affichage de manière plus élaborée qu'un simple libellé, par la présentation de données multiples en colonnes (voir l'image 4 ci-dessous). - Selection multiple (attribut
multiple
) : le composant autoComplete accepte également la selection de plusieurs suggestions comme illustré à l'image 4 ci-dessous.Image 4 : composant autoComplete avec affichage personnalisé des suggestions et sélection multiple.
- Liste déroulante (attribut
dropdown
) : le composant autoComplete peut être configuré pour se comporter comme une liste déroulante et afficher à la demande la liste des valeurs possibles (voir l'image ci-dessous).Image 5 : liste déroulante autoComplete.
- Info-bulles (balise
<f:facet name="itemtip">
) : une info-bulle peut être affichée au survol des suggestions pour fournir à l'utilisateur des précisions complémentaires (voir l'image ci-dessous).Image 6 : composant autoComplete avec info-bulles sur les suggestions.
- Affichage instantanée de la sélection (balise
<p:ajax/>
) : l'évènement de sélection d'une suggestion peut être intercepté pour émettre une requête AJAX à destination du managed bean sans avoir à attendre la validation explicite du formulaire par l'utilisateur.
Le code source de l'application demo_autocomplete
Le code source de l'application web demo_autocomplete
pré-configuré pour GlassFish et Ant peut être téléchargé sous la forme d'une archive ZIP en cliquant sur le lien ci-dessous :
- Application web demo_autocomplete.zip
Ce code source vous est mis à disposition uniquement à titre expérimental et à des fins éducatives. Vous restez par conséquent seul responsable de l'utilisation que vous en faites.