Scriptaculous Ajax.Autompleter : situation réelle : suggestions dynamiques pour la saisie d'une ville
Par Pascal MARTIN le dimanche 4 novembre 2007, 10:30 - Développement Web - Lien permanent
Nous avons vu dans un article précédent comment créer une liste de suggestions à partir d'une saisie utilisateur.
Au cours de cet article, nous avons appris à utiliser le composant Ajax.Autocompleter du framework Javascript Script.aculo.us, lui-même basé sur la librairie prototype.js, pour répondre à des besoins basiques.
Nous allons maintenant voir comment l'utiliser dans une situation plus proche d'un cas "réel" :
- saisie de plusieurs informations
- utilisation d'une base de données côté serveur
- grand nombre de données
- affichage d'informations supplémentaires en cours de saisie
- récupération et exploitation de la donnée saisie par l'utilisateur :
- côté client, pour traitements Javascript pendant l'utilisation du formulaire,
- et côté serveur, une fois le formulaire validé.
Côté client : HTML et CSS
Comme bien souvent, commençons par une page HTML, que viendront enrichir une CSS pour la présentation, puis du JavaScript pour le comportement :
Page HTML incluant un formulaire
Côté client, nous avons besoin d'une page HTML.
Celle-ci doit contenir un formulaire, lui-même incluant un champ input text, où l'utilisateur pourra saisir la donnée attendue.
Ici, ce champ a pour identifiant et nom villes.
Un élément HTML doit aussi être prévu pour permettra l'affichage des suggestions.
Ici, il s'agit d'un <div>, d'identifiant villes_propositions.
Nous avons aussi inclut un indicateur de chargement : l'élément d'id indicateur-chargement-ville, masqué au chargement de la page.
Il sera rendu visible en Javascript, lors du chargement de données depuis le serveur, pour indiquer à l'utilisateur qu'il se passe "quelque chose".
<form action="serveur-action.php" method="post"> Saisissez des noms de villes (séparateur : ',' ou ';') : <br /><input type="text" id="villes" name="villes"/> <span id="indicateur-chargement-ville" style="display: none;">?</span> <br /><span id="code_insee" style="display: none;"></span> <div id="villes_propositions" class="autocomplete"></div> <div> <input type="submit" name="ok" value="Hop !" /> </div> </form>
Naturellement, il nous faut inclure les bibliothèques Javascript dont nous avons besoin :
- Le Framework prototype.js,
- les fichiers
effects.jsetcontrol.js, de la bibliothèque graphique Script.aculo.us, - et le fichier
client-1.js, où nous placerons nos scripts.
<script type="text/javascript" src="lib/prototype.js"></script> <script type="text/javascript" src="lib/effects.js"></script> <script type="text/javascript" src="lib/controls.js"></script> <script type="text/javascript" src="client-1.js"></script> <script type="text/javascript"> window.onload = init; </script>
Les dernières lignes nous permettent de déclencher une fonction nommée init une fois la page chargée[1].
Et un peu de CSS pour plus de style
Pour éviter que le rendu ne soit trop spartiate, voici quelques lignes de CSS que vous pouvez mettre en place :
div.autocomplete { position: absolute; width: 500px; background-color: white; border: 1px solid #888; margin: 0px; padding: 0px; } div.autocomplete ul { list-style-type: none; margin: 0px; padding: 0px; max-height: 20em; overflow: auto; } div.autocomplete ul li.selected { background-color: #ffb; } div.autocomplete ul li { list-style-type:none; display: block; margin: 0; padding: 2px; cursor: pointer; } div.autocomplete ul li span.informal { color: grey; }
La seule différence introduite par rapport à ce que je proposais dans mon premier article à propos de Ajax.Autocompleter est l'ajout de la dernière définition : chaque élément de classe informal situé dans la liste de propositions apparaîtra en gris, au lieu du noir de la suggestion en elle-même.
Vous comprendrez le pourquoi de cette règle un peu plus tard.
Côté client : Javascript et Autocompleter
La création de l'instance d'objet AJax.Autocompleter se fait, dans le principe, de la même manière que lors de notre précédent article.
La différence est que nous avons rajouté plusieurs options :
// Instanciation de la classe Autocompleter, pour le champ de saisie "villes" new Ajax.Autocompleter( "villes", // id du champ de formulaire "villes_propositions", // id de l'élément utilisé pour les propositions "serveur-ville-3.php", // URL du script côté serveur { paramName: 'ville', // Nom du paramètre reçu par le script serveur minChars: 2, // Nombre de caractères minimum avant que des appels serveur ne soient effectués tokens: [',', ';'], indicator: 'indicateur-chargement-ville', afterUpdateElement : function (input, li) { // Fonction appelée après choix de l'utilisateur $('code_insee').innerHTML = 'Code INSEE choisi : ' + li.id; $('code_insee').show(); } });
Au programme des nouveautés :
- Les appels Ajax vers le serveur ne sont plus effectués dès que l'utilisateur effectue une saisie, mais seulement à partir du moment où il a déjà saisi deux caractères : option
minChars. - L'utilisateur peut effectuer plusieurs saisies au sein du même champ de formulaire ; ces saisies devront être séparées par des virgules ou des point-virgule : option
tokens. - Un indicateur de chargement sera affiché pendant l'exécution des requêtes Ajax en arrière-plan : option
indicator- Vous devez définir pour cette option l'identifiant de l'élément à afficher ou à masquer
- L'affichage s'effectuera via un appel à la méthode Element.show de Prototype,
- et l'élément sera ensuite masqué via un appel à la méthode Element.hide.
- L'option
afterUpdateElementpermet de définir une fonction qui sera appelée après que l'utilisateur ait effectué un choix dans la liste de suggestions.
La fonction appelée lorsque l'option afterUpdateElement est définie attend deux paramètres :
- Le champ de saisie,
- et l'élément sélectionné dans la liste de sélections.
- Attention, il s'agit bien de l'élément
<li>entier, et non simplement de son contenu ou de son identifiant !
- Attention, il s'agit bien de l'élément
Ici, la fonction que nous avons défini affiche un message contenant le code INSEE de la ville sélectionnée.
(Ce du fait que nous avons renvoyé depuis le serveur, pour chaque élément de la liste de suggestions, le code INSEE en identifiant des-dits éléments)
Côté serveur : script appelé par l'Autocompleter
Le principe du champ de saisie et de la liste de suggestions que nous mettons en place est simple :
- L'utilisateur commence à saisir du texte - un début de nom de ville, dans l'idéal,
- et nous lui affichons la liste des villes dont le nom commence par sa saisie.
Cela implique que, côté serveur, nous ayons à disposition une liste de villes.
Dans un cas "réel", cette liste de villes serait stockée au sein d'une base de données, et les données seraient chargées via une requête SQL...
... Et bien, c'est exactement ce que nous ferons ici !
Base de données de villes
Pour cet article, j'ai choisi d'utiliser une base de données SQLite, pour la simplicité de mise en place.
Voici quelques lignes de code permettant de la créer.
Les noms et type des colonnes étant aisément lisible depuis le code PHP fourni, je ne détaillerai pas :
<?php $sqliteerror = null; if ($db = sqlite_open('villes.sqlite.db', 0666, $sqliteerror)) { //$result_drop = sqlite_exec($db, 'drop table villes'); $query_create_table = <<<QUERY_CREATE_TABLE create table villes ( code_insee varchar(5) primary key, code_postal varchar(5), code_departement varchar(2) not null, libelle varchar(64) not null, latitude float, longitude float ) QUERY_CREATE_TABLE; $result_create = sqlite_exec($db, $query_create_table); } ?>
Les colonnes code_departement, latitude, et longitude ne nous servirons pas pour cet article - il s'agit d'une base de données que j'ai mis en place pour une autre situation, et que je réutilise ici.
Si vous ne souhaitez pas les reproduire, il vous faudra peut-être modifier quelques portions de codes au sein des exemples fournis plus bas.
En terme de contenu, voici ce que cela peut donner :
sqlite> select * from villes limit 0, 10; code_insee code_postal code_departement libelle latitude longitude ---------- ----------- ---------------- ---------------------- ---------- ---------- 01001 01400 01 Abergement-Clémenciat 46.123722 5.011565 01002 01640 01 Abergement-de-Varey 46.029120 5.411734 01004 01500 01 Ambérieu-en-Bugey 45.979848 5.336887 01005 01330 01 Ambérieux-en-Dombes 45.990500 5.016713 01006 01300 01 Ambléon 45.723066 5.663965 01007 01500 01 Ambronay 45.979848 5.336887 01008 01500 01 Ambutrix 45.979848 5.336887 01009 01300 01 Andert-et-Condon 45.723066 5.663965 01010 01350 01 Anglefort 45.852517 5.752835 01011 01100 01 Apremont 46.244422 5.652013
Je ne produirai pas le contenu entier de cette table ici... A raison de plus de 36600 lignes, ce serait difficile...
Néanmoins, pour les besoins des exemples qui suivent, je joins à cet article un fichier contenant les requêtes d'insertion nécessaires pour charger[2] :
- La liste des villes commençant par 'l',
- la liste des villes commençant par 'pa',
- et la liste des villes commençant par 'beaure'
Pour information, la liste de toutes les communes de France peut se télécharger depuis le site de l'INSEE, ou depuis le site de l'IGN.
La liste des codes postaux est trouvable en fouillant un peu via les moteurs de recherche ; malheureusement, je n'ai pas réussi à trouver une liste "officielle"[3]...
Script PHP chargeant les données depuis celle-ci, et les renvoyant à l'Autocompleter
L'instance d'objet Ajax.Autocompleter, configurée comme plus haut, effectue une requête Ajax vers un script sur le serveur à partir du moment où l'utilisateur a saisi plus d'un certain nombre de caractères ; plus de 1 caractère, ici.
Ce script reçoit un paramètre dont le nom correspond à celui déclaré comme paramName plus haut dans notre code Javscript ; ici, ville.
Et ce paramètre a pour valeur la saisie actuelle de l'utilisateur - ici, typiquement, un début de nom de ville.
Globalement, notre script va charger depuis une base de donnée la liste des villes dont le libellé commence par la saisie, et va renvoyer cette liste.
Les données en sortie seront organisées sous forme d'une liste non-ordonnée, comme attendues par la classe Ajax.Autocompleter :
<?php // Pour faciliter les choses (dont le debug !), accepte des données soit en GET, soit en POST // => Eventuellement, à modifier une fois le debug terminé. $saisie = (isset($_POST['ville']) ? $_POST['ville'] : (isset($_GET['ville']) ? $_GET['ville'] : '')); if (strlen($saisie) > 1) { // En théorie, script jamais appelé avec une saisie de moins de 1 caractère... // Mais autant s'en assurer : sans ça, on sélectionne le contenu de // tout la table... Et boum ! $sqliteerror = null; if ($db = sqlite_open('data/villes.sqlite.db', 0666, $sqliteerror)) { $saisie_escaped = sqlite_escape_string($saisie . '%'); $query = "select * from villes where libelle like '$saisie_escaped' order by libelle"; $result = sqlite_query($db, $query); echo "<ul>\n"; while ($obj = sqlite_fetch_object($result)) { $informal = (!empty($obj->code_postal) ? '<span class="informal"> (' . $obj->code_postal . ')</span>' : ''); echo "<li id=\"{$obj->code_insee}\">{$obj->libelle}$informal</li>\n"; } echo '</ul>'; sqlite_close($db); } else { // Pour débugguer //die($sqliteerror); // Pour indiquer à l'Autocompleter qu'il n'y a rien à afficher : echo "<ul>\n"; echo '</ul>'; } } ?>
Un point à noter : l'objet Ajax.Autocompleter permet de définir, pour chaque élément de la liste retournée, un sous-élément de classe "informal".
La donnée placée au sein de ce bloc sera affichée dans la liste de suggestions, mais ne sera pas reproduite dans le champ de saisie, une fois que l'utilisateur aura choisi l'une des suggestions.
Ici, j'utilise cette fonctionnalité pour indiquer à côté de chaque libellé de ville son code postal.
Ajax.Autocompleter admet aussi que nous définitions, pour chaque élément <li>, un identifiant : "id".
Si cet attribut est renseigné, la valeur placée là doit permettre d'identifier, de manière unique, quelle est la suggestion sélectionnée par l'utilisateur.
Dans notre cas, l'identifiant unique de chaque ville est son code INSEE - c'est d'ailleurs la clef primaire de notre table.
Captures d'écran du résultat
Pour vous donner une idée de ce que ça donne, voici maintenant quelques captures d'écran du résultat, une fois le tout assemblé :
Saisie de "ly", qui provoque l'affichage en suggestions de toutes les villes commençant par ces deux caractères :
Ajout d'un caractère à la saisie ; la liste de suggestions est donc filtrée en tenant compte de cette nouvelle donnée, plus précise :
Et saisie de "lyon", sans effectuer au choix depuis la liste de suggestions :
(ça se voit à la non-présence de majuscule dans le champ de saisie, alors que la majuscule à "Lyon" dans la liste de suggestions était présent sur la capture précédente)
En même temps, début de saisie d'un second libellé de ville, après une virgule comme séparateur :
Et une fois le choix effectué au sein de la liste de suggestions :
Une fois le formulaire entier validé (bouton "Hop"), nous obtenons, côté serveur, les données suivantes :
(Il s'agit d'un var_dump de $_POST - avec l'extension Xdebug d'installée[4].)
Exemple de traitement à la soumission du formulaire
Maintenant que nous avons vu comment exploiter les possibilités de l'objet Ajax.Autocompleter côté client, il nous reste à récupérer les saisies effectuées par l'utilisateur, une fois le formulaire validé.
Pour terminer cet article, voyons comment vous pourriez récupérer ces données saisies... Et quelques problématiques qui peuvent se poser.
Cas de tests
Considérons le cas où l'utilisateur a effectué la saisie suivante :
Voici quelques cas auxquels il convient de penser :
- "lyon" est une ville existante en base, mais l'utilisateur l'a saisi à la main, sans cliquer sur une suggestion de la liste, et sans respecter la casse (ça se voit à la minuscule : dans la liste de suggestions, "Lyon" était proposée avec une majuscule, alors que, finalement, la saisie finale est toute en minuscule),
- "Paris-l'Hôpital" a sans aucun doute été choisi parmis des propositions d'une liste de suggestions,
- " " est une saisie vide : on a deux séparateurs de saisie, un espace entre, mais pas de saisie réelle - c'est un cas qu'il faut prévoir de gérer,
- "glop" est de toute évidence une saisie ne correspondant pas à une ville existante
- et "beaurepaire" est un nom qui correspond à plusieurs villes.
Au cours de vos tests, vous en identifierez peut-être d'autres ; à vous de vous assurer qu'ils soient bien gérés.
Principe de fonctionnement côté serveur
Côté serveur, probablement, vous voulez :
- Enregistrer dans un champ d'une table la saisie de l'utilisateur, en particulier dans le cas où il ne s'agit pas d'une ville connue.
- Si la saisie correspond à une ville "connue", vous voudrez probablement enregistrer aussi le code INSEE de celle-ci ; peut-être aussi son code postal ?
- Et enfin, si la saisie correspond à plusieurs villes... Et bien, à vous de voir comment faire !
C'est un peu l'idée suivie par le script suivant :
- Si le formulaire a été validé,
- Ouverture d'une connexion vers une Base de Données (la même que plus haut, en l'occurence)
- Découpage du champ de saisie selon les séparateurs admis : '
,' et ';'. - Parcours de la liste de saisie ainsi obtenue
- Pour chaque saisie :
- "Nettoyage" : suppression des éventuels espaces en début et fin de chaine
- Conversion en minuscule pour test en DB un peu plus loin
- Conservation uniquement des saisies non-vides - Gestion du cas de la saisie " "
- Échappement de la saisie, pour éviter les injections SQL
- Tentative de chargement des données de la ville depuis la DB :
- Gestion des trois cas :
- Ville non trouvée,
- Ville trouvée une fois,
- Ville trouvée plusieurs fois.
<?php if (isset($_POST['ok'], $_POST['villes'])) { $sqliteerror = null; if (($db = sqlite_open('data/villes.sqlite.db', 0666, $sqliteerror))) { // On a permis la saisie de plusieurs villes, séparées par ',' ou ';' $villes = preg_split('/[,;]/', $_POST['villes']); foreach ($villes as $ville) { $ville = strtolower(trim($ville)); if ($ville !== '') { // Suppression des éventuelles saisies vides if (get_magic_quotes_gpc()) { $ville = stripslashes($ville); } $ville_escaped = sqlite_escape_string($ville); $query = "select * from villes where lower(libelle) = '$ville_escaped'"; $result = sqlite_query($db, $query); $nbr_results = sqlite_num_rows($result); if ($nbr_results == 0) { // Aucune ville trouvée pour ce libellé => saisie libre echo "<strong>Ville '$ville' non trouvée</strong><br />"; } else if ($nbr_results == 1) { // Une ville trouvée pour ce libellé => ok echo "<strong>Ville '$ville' trouvée :</strong><br />"; $obj = sqlite_fetch_object($result); echo "{$obj->code_insee} => {$obj->libelle}<br />"; } else { // Plusieurs villes trouvées pour ce libellé... Etrange echo "<strong>Ville '$ville' trouvée plusieurs fois :</strong><br />"; while ($obj = sqlite_fetch_object($result)) { // Affichage de chacun des villes echo "{$obj->code_insee} => {$obj->libelle}<br />"; } } } } // Fin parcours des saisies sqlite_close($db); } else { die($sqliteerror); } } else { die("'ok' et 'villes' attendus."); } ?>
Quelques remarques :
- J'ai préféré tester si les
magic_quotessont activées, et, si oui, dés-échapper la saisie, pour l'échapper ensuite quoi qu'il en soit, à l'aide de la fonction spécifique à SQLite. - Je suis passé par une double conversion en minuscules :
- Conversion en minuscules de la saisie avant recherche en base,
- Conversion en minuscule des libelles de villes en base au sein de la requête - ce qu'il ne faut absolument pas faire : avec ça, on se tappe un parcours de toutes les lignes de la table, dans tous les cas, plutôt que de profiter d'un éventuel index sur la colonne libelle !
- Ce du fait que je ne connais, à l'heure où j'écris cet article, qu'assez mal SQLite : j'ai l'impression que la collation par défaut est sensible à la casse, et qu'on ne peut définir de collation qu'à la création de la table... Et je n'ai pas le courage de supprimer / re-créer la-dite table.
- Dans un cas réel, considérant les besoins de cette application, la colonne libelle utiliserait une collation insensible à la casse, insensible aux accents[5], et serait indexée.
Quoi qu'il en soit, avec les saisies présentées plus haut, voici quelle serait la sortie du script :
(Présentation HTML non reproduite)
Ville 'lyon' trouvée : 69123 => Lyon Ville 'paris-l'hôpital' trouvée : 71343 => Paris-l'Hôpital Ville 'glop' non trouvée Ville 'beaurepaire' trouvée plusieurs fois : 38034 => Beaurepaire 60056 => Beaurepaire 76064 => Beaurepaire 85017 => Beaurepaire
Cas d'un libellé correspondant à plusieurs villes
Nous avons eu un peu plus haut un cas qui nous pose problème : le libellé "Beaurepaire" correspond à plusieurs villes, toutes éloignées géographiquement les unes des autres :
sqlite> select * from villes where libelle = 'Beaurepaire'; code_insee code_postal code_departement libelle latitude longitude ---------- ----------- ---------------- ----------- ---------- ---------- 38034 38270 38 Beaurepaire 45.359931 5.030076 60056 60700 60 Beaurepaire 49.315466 2.601329 76064 76280 76 Beaurepaire 49.631195 0.239438 85017 85500 85 Beaurepaire 46.873793 -1.042068
Une fois ces villes reportées sur une carte, voila ce qu'on obtient :
Effectivement, aucune de ces villes n'est qu'un "doublon" d'un autre : elles sont toutes bien distinctes !
(Et j'en ai déjà vu deux par moi-même ; je confirme, il faut du temps pour aller de l'une à l'autre ^^)
Dans ce genre de cas, avec le seul libellé, nous n'avons aucune manière d'identifier, une fois le formulaire validé, quelle ville avait été choisie par l'utilisateur...
Plus haut, nous avons affiché à l'écran, lors du choix de l'utilisateur, le code INSEE de la commune choisie.
Nous pourrions stocker celui-ci dans un champ caché du formulaire, afin de le récupérer côté serveur une fois le formulaire validé.
Bien entendu, il faudrait gérer le fait que nous avons permis à l'utilisateur, pour cet article, de saisir plusieurs libellés de ville au sein du même champ de saisie... Était-ce une bonne idée ?
Et dans le cas où l'utilisateur saisi entièrement à la main un libellé correspondant à plusieurs communes, sans le sélectionner dans la liste de suggestions, nous n'avons pas la possibilité d'identifier à quelle ville l'utilisateur voulait effectivement faire allusion... Faut-il lui afficher un écran intermédiaire, en le forçant à saisir, parmis les quatre "Beaurepaire" en France, la ville qui l'intéresse ?
(Et si c'était plus d'une ?[6] )
Et encore une autre question : comment pourriez-vous gérer le fait qu'un libellé corresponde, en France, à une ville... Mais que ce libellé corresponde aussi à un nom de ville dans un autre pays ?
(Et que, bien entendu, votre utilisateur veuille désigner cette ville là, et non celle en France ! Voila qui pose problème par rapport à l'enregistrement du code INSEE ou du code postal, non ? [7] )
Une liste de suggestions "à la volée" présente bien des avantages... Sinon, vous ne seriez pas en train de lire cet article...
Mais un Autocompleter ne répond pas nécessairement à tous les besoins. Pensez-y avant de commencer à développer !
Notes
[1] Oui, il y a des façons plus propres de brancher un appel de fonction sur le chargement de la page, mais ce n'est pas le but de cet article. Au besoin, vous pouvez consulter, par exemple, Closures and executing JavaScript on page load - ou lancer une recherche via votre moteur de recherche favoris...
[2] Notez que le fichier d'ordre SQL d'insertion joint est encodé en UTF-8 ; si vous n'y prenez pas garde, vous rencontrerez éventuellement des problèmes d'accents mal affichés
[3] Si vous connaissez un site sur lequel on peut télécharger gratuitement la liste "officielle" des codes postaux de toutes les communes de France, je suis intéressé !
[4] Extension Xdebug, que je conseille pour tout poste de développement PHP !
[5] Est-ce possible en SQLite ?
[6] Oui, je cherche les ennuis :p
[7] Si ^^
Pour être averti lors de la publication de nouvelles entrées, n'hésitez pas à vous abonner au flux RSS ou ATOM des articles de mon blog !







Commentaires
Bravo pour l'article, utile pour moi en tout cas
Petite question : existe-t-il une solution simple pour laisser ouverte la liste des choix de l'autocompleter tant qu'un choix n'a pas été fait dans la liste proposée, ou que l'utilisateur n'a pas supprimer des caractères pour être inférieur à minChars ?
Je voudrais obtenir un résultat proche des input autocomplete de la boite 'Rechercher un vol' du site http://www.nouvelles-frontieres.fr/
La différence entre l'Autocompleter de Scriptaculous et celui sur nouvelles-frontieres, c'est que celui de nouvelles-frontieres ne disparait pas quand le champ de saisie perd le focus, à priori ?
(Note : mes exemples utilisent la syntaxe de prototype.js 1.5 + scriptaculous 1.7, sur laquelle cet article est basé ; certains points au niveau des héritage de classes ont été modifiés sous prototype 1.6 ; mes exemples devront peut-être donc être adaptés.)
(Note 2 : Testé uniquement sous Firefox avec l'extension Firebug installée)
Si c'est ce comportement que tu veux reproduire, on doit pouvoir s'en sortir d'au moins trois façons différentes :
1: Modifier la bibliothèque
On peut commencer brutalement, en modifiant Scriptaculous pour que ça réponde à tes besoins.A première vue, il faudrait modifier le fichier control.js :
soit supprimer
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));Dans
Autocompleter.Base.prototype.baseInitialize,soit patcher
Autocompleter.Base.prototype.onBlurpour que ça réponde à tes besoins. (typiquement, en virant l'appel à hide dans le setTimeout)Mais modifier comme ça une bibliothèque, c'est un peu violent ^^
Et la modification risque d'être perdue le jour où tu (ou un autre développeur sur ton projet) mettra à jour Scriptaculous.
Note : je n'ai absolument pas testé - pas une solution qui me plaise
2: Redéfinir Ajax.Autocompleter.prototype.onBlur
Une fois la bibliothèque inclue, tu peux redéfinie la fonctionAjax.Autocompleter.prototype.onBlur, pour qu'elle n'aie plus son comportement par défaut, mais un comportement que tu définis.Par exemple :
Ajax.Autocompleter.prototype.onBlur = function (){
console.log('blur');
};
Une fois que ceci est fait, tous les objets
Ajax.Autocompleterafficheront '' dans la console Firebug, au moment où le champ de saisie perd le focus, plutôt que de masquer la liste de suggestions.L'inconvénient est que si quelqu'un crée une instance d'
Ajax.Autocompleterplus loin sur la même page, son Autocompleter aura le même comportement (non-standard par rapport à la bibliothèque) que le tient.3: Définir une nouvelle classe au comportement spécifique
Troisième solution : tu définis une nouvelle classe, qui hérite deAjax.Autocompleter, pour en reprendre tout le fonctionnement, et qui surcharge juste la méthode onBlur :var MyAutocompleter = Class.create();Object.extend(Object.extend(MyAutocompleter.prototype, Ajax.Autocompleter.prototype), {
onBlur: function ()
{
console.log('blur');
}
});
Ensuite, tu continue à utiliser
Ajax.Autocompleterquand tu veux le comportement "standard", et tu utilisesMyAutocompleterquand tu veux le comportement spécifique.Dans le cas où tu veux mettre en place ce comportement sur toute ton appli, la seconde solution me paraît préférable (du moins si tu utilises déjà partout des Ajax.Autocompleter).
La première solution est à mes yeux à bannir.
Et la troisième solution aurait tendance à être la plus "propre" ^^
En espérant que ça réponde à ta question,
Have Fun !
Bonjour,
Merci pour cet excellent tutorial qui a levé un peu le brouillard face à l'integration de scriptaculous.
Une ligne de ton article m'a pas mal intrigué et remis pas mal de choses en cause : "Conversion en minuscule des libelles de villes en base au sein de la requête - ce qu'il ne faut absolument pas faire : avec ça, on se tappe un parcours de toutes les lignes de la table, dans tous les cas, plutôt que de profiter d'un éventuel index sur la colonne libelle !". En effet, j'ignorais tout de ça, et je suis bien embeté car le champ de ma table sur laquelle je fais mon lower est en effet indexé (et c'est indispensable qu'il le soit), et le lower est fait dans plusieurs de mes traitements (pour rendre les recherches accents et casse insensible).
j'ai eu beau chercher un peu sur le web mais je ne trouve rien qui confirme ou non ce que tu dis. Aurais tu des sources et eventuellement des solutions pour une recherche accents et casse insensible sur champ indexé ?
Merci par avance
Excellent article.
Vous auriez aussi pu évoquer le problème sous Internet Explorer quant au div d'auto-suggestion qui disparait au mauvais moment.
Merci pour votre excellent tutorial.
Cependant j'ai du mal a trouver une base de donnee complete des villes correspondant a votre schema.
Serait-il possible que vous puissiez me mailer la votre ?
Merci encore.
Bonsoir,
Une base de données avec
Ce n'est pas quelque chose de "facile" à trouver, vous avez du vous en rendre compte ^^
Pour obtenir la Base que je prenais comme exemple pour cet article, je suis parti des fichiers de l'INSEE (Cf par exemple http://www.insee.fr/fr/methodes/nomenclatures/cog/telechargement.asp ), que j'ai recoupé avec ceux de l'IGN (Cf notamment http://professionnels.ign.fr/ficheProduitCMS.do?idDoc=5323862 ), me semble-t-il ; et j'ai ensuite effectué un bon paquet de requêtes vers le service de Geocoding de Google Maps...
Dans chacun des cas, à vous de voir si les licences d'utilisation de ces fichiers collent avec l'utilisation que vous voulez en faire, cela dit ^^
Mais c'est une solution qui n'est pas parfaite : certaines communes sont mal localisées, d'une part, et la base obtenue ne contient pas certains alias qui sont parfois plus connus que les villes qu'ils désignent réellement
Typiquement, une Base de communes obtenue de cette manière demandera régulièrement des corrections : il peut être bon de permettre aux utilisateurs d'indiquer qu'une commune est mal géolocalisée, ainsi que de jouer quelques requêtes sur la DB pour essayer de déterminer si certaines communes ne sont pas "loin" de là où on pourrait "raisonnablement s'attendre à ce qu'elles soient"...
Enfin, pour faire bref, je ne vous enverrai pas la DB que j'avais constitué pour cet exemple, mais vous devez à présent avoir en main tous les éléments nécessaires pour la reconstituer -- et faire mieux
Bon courage !
Bonjour, très sympa ton tuto !!
J'ai néanmoins un problème tout fonctionne parfaitement excepte à la toute première auto-completion.
C'est à dire juste après le chargement de la page je tape 3 lettres, l'indicator m'indique le chargement puis disparaît mais rien d'autre ne se passe.
Il suffit alors que je rajoute ou enlève une lettre (ou même que j'appuie sur la flèche du bas) pour qu'il me propose des villes.
Des lors tant que je ne recharge pas la page tout marche parfaitement quoi que je fasse.
On dirait que le script à besoin de tourner une fois à vide pour bien marcher ce qui enlève tout l'intérêt de l'autocompletion !
Je précise que je suis sous IE !
Avez vous des idées ??
Pour compléter l'article, je voudrais ajouter ce bout de code.
Il permet l'autocomplétion d'autres champs associés au champ autocompleté.
La reponse serveur est du type:
Le champ title indique le nom du champ a completer.
Libre a vous de modifier selon vos necessités
Cordialement
Cedric
ps: dsl mais le code s'affiche bizarrement ; meme avec la balise code :/
Bonsoir,
Oui, la balise "code" ne fonctionne pas vraiment pour l'ajout de commentaires
=> Je suis souvent plus ou moins obligé de repasser sur les portions de code, pour les rendre correctement présentées... (Il faut vraiment que je débuggue ce truc, un jour... )
Quoi qu'il en soit, merci
Et bonne continuation !
Salut, Super Tuto merci : )
Le code de cédric au dessus m'intéresse fortement mais je n'arrive pas à l'utiliser !!
je copie le code à la fin de fichier control.js et j essaye de l'utiliser mais cela ne marche pas !!!
Faut t'il que je crée une nouvelle class surchargée ?? Je ne comprends pas trop.
Merci de m'aider si vous avez une idée.
Parfait merci pour ce tuto.
Question : comment faire si je souhaite pouvoir afficher dans un select, la liste des villes donc le CP aurrait été saisi dans un input (affichage de la liste sur un onblur) ?
Encore Merci
Yep!
Pascal: oui, c'est plus beau comme ca
moo:
Tu peux créer un autre fichier .js qui se charge après scriptaculous.
En fait, ca "sauvegarde" les fonctions écrites par scriptaculous et remplace par des fonctions personnalisées (qui appellent les originales aussi...).
C'est effectivement une surcharge. Quel est ton erreur? Je te suggère Firebug si tu ne l'utilises pas encore.
Topheur:
L'affichage est une DIV; tu peux y mettre tout ce que tu veux... Tu n'es pas obligé de renvoyer uniquement un seul champ.
A l'extrême, tu pourrais même taper soit le code postal, soit une partie de la ville et le serveur te renverrais les deux (comme dans l'exemple)...
Cédric
(j'aime quand il y a des échanges en commentaire, et que mes visiteurs se répondent entre eux ! Merci
)
C'est un plaisir Pascal
Merci beaucoup Cédric pour ta réponse mais même si je comprend bien le principe(surcharge et redéfinir les méthodes) j'ai du mal à comprendre ton petit bout de code vu que je ne connais presque pas javascript !!!
Je ne comprend pas bien les 2 premières lignes !
En fait tu récupère les méthodes "initialize" et "updateElement" de la classe mère ?
Quelle est l'utilité de faire cela ? L'Objet.extend() n'est t'il pas sensé passer toute les méthodes de la classe mère ?
Ensuite tu utilise Object.extend(destination, source) et dans la destination tu redéfini seulement les méthodes "initialize", "onFocus" et "updateElement". C'est ca ??
Ensuite pour utiliser la classe surchargé on l'instancie avec une option afterUpdateElement ou la fonction updateElement sera effectué automatiquement pour toute les retours du script ?? :
new Ajax.Autocompleter("ville","villes_propositions","script_serv.php",
{ paramName: 'ville', minChars: 2, indicator: 'indicateur-chargement-ville', afterUpdateElement : function (input, li) { Ajax.Autocompleter.prototype.updateElement(li); } });
Voila désolé d'avance pour toute les aberrations que j'ai du écrire ^^
Je ne peux pas te poster les erreurs car je n'ai pas accès aux fichier la, mais je le ferais des que je peux : )
Voila tout plein d'interrogations ^^ J'espère que tu pourra m'aider !
Ps !!! (oui c'est long désolé : )
si je veux remplir un champs "departement" associé à la ville choisie par mon utilisateur le contenu de ma boucle sur mon script serveur devra etre comme ca ? :
echo ' <ul><li id= \"' . $id . '\" > ' . $ville . ' <span > ';
echo ' <span title="departement"> ' . $departement . ' </span> ';
echo ' </span ></li></ul> ';
Encore Merci !!! Et si j'abuse dite le moi : )
Par exemple pour "initialize", vu que je veux ajouter un événement lors de la création de l'objet tout en gardant ce qui existait déjà, je fais appel d'abord à la version original (sauvée sous "__initialize") et j'ajoute la ligne qu'il me faut (ici le 'Event.observe').
Dans mon code, je n'étais pas obligé de sauver en "__updateElement" vu que je ne l'utilise pas. Dans ce cas la; j'ai modifié le code original de la classe mère...
Un exemple:
Voici les deux champs complétés en plus de l'autocomplete:
Avec l'objet autocomplete créé sur l'element "form_ville".
Retourné par le serveur:
Donc ton exemple est bon a condition que l'id de ton input soit "departement".
Cordialement
Cédric
Bonjour,
Merci pour votre article, la suggestion des villes pourrait m'être utile pour mon moteur de recherche des zones desservies par les cybermarchés (actuellement recherche par CP).
Bonjour,
Très bon tuto et très bon complément de Cédric, bravo a tous les 2!
Ca m'a permis de faire un formulaire d'inscription.
J'ai une petite question, à la place d'afficher le code INSEE, j'aimerai afficher par exemple le nom de la ville?
J'ai bien vu qu'il faut changer ca : "li.id" qui se trouve dans cette ligne :
$('code_insee').innerHTML = 'Code INSEE choisi : ' + li.id;
mais je n'y arrive pas... si quelqu'un pouvait m'aider svp!
J'ai une erreur avec le code de jack :
Erreur : this.getTokenBounds is not a function
Quelqu'un sait d'ou ca vient?
Bonjour,
Ca marche du tonnerre ! Un grand merci pour le tuto.
J'ai modifié l'afterUpdateElement pour que l'envoi de la sélection soit direct.
Pour ma part j’ai ajouté une recherche avec latitude/longitude et une carte inréractive, pour connaître les villes les plus proches du point central. J'ai mis en place 3 bases de données : une pour les départements et les préfectures, une autre pour les villes et positions (35000 points), et les adresses des pizzerias affiliées ou pas au concept www.pizzatoy.com.
L’intérêt d’une telle application est multiple. On s’approche du logique de géomarketing (Géolocalisation et distance entre des villes) en délimitant une zone de chalandise. On peut y adapter un curseur pour faire varier le rayon de notre recherche. Un des points intéressant est que, par exemple, une ville limitrophe de plusieurs départements renverra un balayage complet de sa zone en une seule recherche; alors qu’un simple moteur par départemnt obligera de relancer la procédure.
Encore merci
Bonjour,
et bravo pour ce superbe tuto qui marche à merveille, même avec les nouvelles versions de scriptaculous et prototype...que d'heures gagnées !
seul défaut : je cherche aussi à appliquer l'autocompletion sur les villes de france (fichier insee) donc grosse volumétrie, et je trouve les temps de réponse très longs !!! quand je tappe une ville avec peu de réponses ça va encore (quoique) mais quand je tappe "saint" alors là...j'ai même le message ie qui me propose de fermer la fenetre car le script est susceptible de ralentir l'ordinateur ! (j'ai 1 Go de ram pourtant)...
Pascal comment fais tu pour avoir des temps de réponse satisfaisants ? j'envisage même de stocker les villes du monde alors je n'ose même pas l'envisager avec les temps de réponse actuel.
PizzaToy, est-ce que c'est envisageable de m'expliquer comment tu as fait pour la géolocalisation ? j'aimerai bien faire de même ! et où as-tu trouver les codes longitude/latitude ?
MERCI DE VOTRE AIDE !
HB
Pouvez-vous m'apporter votre aide si vous avez eu le même problème ?
> glachanta
Tu peux voir ces 2 sites il y a les tutos qu'il te faut :
www.awelty.fr
www.lion1906.com
@+