I. Introduction

Les sites web évoluent avec le temps et deviennent de plus en plus sophistiqués. Le but principal des développeurs web est de simplifié et rendre plus agréable leurs utilisations. Nous allons mettre en place un système de proposition de code postal/ville dès la saisie de l'un de ces deux membres.

Consulter la démo

II. La base de données

Bien entendu nous n'allons pas saisir tous les codes postaux et les villes de France ! Nous avons à notre disposition une liste déjà toute faite sur le site http://www.geonames.org/. Il y a bien évidemment la liste des codes postaux/ville de tous les pays.

Rendez-vous sur la page http://download.geonames.org/export/zip/ puis choisissez le pays que vous souhaitez. Pour ce tutoriel nous prendrons "FR.zip"

Il se comporte de deux fichiers textes. Le premier est un fichier " lisez-moi " qui explique la licence du fichier des codes postaux, mais aussi la structure du fichier des codes postaux. Et le fameux fichier des codes postaux.

Si vous ouvrez le fichier " FR.txt " vous constaterez que c'est une liste séparée par des tabulations, dont voici la structure :

  • Code ISO pays : 2 caractères
  • Code postal : varchar(10)
  • Ville : varchar(180)
  • Nom région : varchar(100)
  • Code région : varchar(20)
  • Nom département : varchar(100)
  • Code département : varchar(20)
  • Nom communauté : varchar(100)
  • Code communauté : varchar(20)
  • Latitude : latitude estimée (wgs84)
  • Longitude : longitude estimée (wgs84)
  • Précision : précision de la latitude et de la longitude, de 1 = estimé à 6 = centré

On va donc créer une table correspondante à la structure de ce fichier.

SQL version complète
Sélectionnez
CREATE TABLE IF NOT EXISTS `cp_autocomplete` (
  `CODEPAYS` char(2) NOT NULL,
  `CP` varchar(10) NOT NULL,
  `VILLE` varchar(180) NOT NULL,
  `NOMADMIN1` varchar(100) NOT NULL,
  `CODEADMIN1` varchar(20) NOT NULL,
  `NOMADMIN2` varchar(100) NOT NULL,
  `CODEADMIN2` varchar(20) NOT NULL,
  `NOMADMIN3` varchar(100) NOT NULL,
  `CODEADMIN3` varchar(20) NOT NULL,
  `LATITUDE` double NOT NULL,
  `LONGITUDE` double NOT NULL,
  `ACURANCY` int(1) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Ou bien tout simplement une version plus légère contenant juste les informations dont nous avons besoin :

SQL version légère
Sélectionnez
CREATE TABLE IF NOT EXISTS `cp_autocomplete` (
  `CODEPAYS` char(2) NOT NULL,
  `CP` varchar(10) NOT NULL,
  `VILLE` varchar(180) NOT NULL,
  KEY `CODEPAYS` (`CODEPAYS`),
  KEY `CP` (`CP`),
  KEY `VILLE` (`VILLE`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Puis nous allons insérer les données, je vous épargne le travail de modification du fichier pour pouvoir l'importer dans votre SGBD, et je vous propose de télécharger le fichier SQL en version complète ou en version légère. Pour la suite du tutoriel, j'utiliserai la version légère.

III. Côté client

On va donc créer une simple page HTML avec deux champs textes, un pour le code postal et l'autre pour la ville.

HTML
Sélectionnez
<!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">
	<head >
		<title>AutoCompletion</title>
	</head>
	<body>
		<form action="#">
			CP :<input type="text" id="cp" size="6"/>
			Ville : <input type="text" id="ville" />
		</form>
	</body>
</html>

Puis nous allons y ajouter la librairie jQuery et jQuery UI.

Mais aussi le CSS pour jQuery UI

HTML
Sélectionnez
<link rel="Stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/themes/base/jquery-ui.css" />

Maintenant que notre structure est en place nous allons implémenter le code pour faire l'autocomplétion.

Tout d'abord, nous déclarons notre autocomplétion sur les deux champs, avec les options de délai de 600milliseconde, pour éviter de trop surcharger notre serveur, mais aussi avec une longueur minimale de 3 caractères, pour les mêmes raisons que l'option précédente.

JavaScript
Sélectionnez
$(function ()
{
	$("#cp, #ville").autocomplete({
		source: function (request, response)
		{
			//TODO
		},
		minLength: 3,
		delay: 600
	});
});

En ce qui concerne la source, nous allons effectuer une requête Ajax vers notre serveur et il nous retournera un format de données JSON. On devra, au minimum, lui envoyé le début du code postal ou de la ville, mais je vais ajouter le code pays et le nombre maximum de valeurs à retourner.

JavaScript
Sélectionnez
var objData = {};
if ($(this.element).attr('id') == 'cp')
{
	objData = { codePostal: request.term, pays: 'FR', maxRows: 10 };
}
else
{
	objData = { ville: request.term, pays: 'FR', maxRows: 10 };
}

Pour fonctionner, l'autocomplétion de jQuery a besoin d'un tableau d'objet sous le format suivant : [{label :'…', value :'…'},{…}]label correspond à la valeur qui sera affichée dans la liste des propositions et value à la valeur qu'il y aura dans le champ une fois la ligne sélectionnée.

On va donc devoir construire ce tableau à partir des données JSON reçu depuis le serveur.

JavaScript
Sélectionnez
$.ajax({
url: "./AutoCompletion.php",
dataType: "json",
data: objData,
type: 'POST',
success: function (data)
{
	response($.map(data, function (item)
	{

		return {
			label: item.CodePostal + ", " + item.Ville,
			value: function ()
			{
				if ($(this).attr('id') == 'cp')
				{
					$('#ville').val(item.Ville);
					return item.CodePostal;
				}
				else
				{
					$('#cp').val(item.CodePostal);
					return item.Ville;
				}
			}
		}
	}));
}
});

Vous pouvez dès à présent essayer de saisir une valeur dans l'un des deux champs et vous observerez, avec firebug par exemple, qu'une requête Ajax est envoyée sur la page AutoCompletion.php avec les paramètres POST suivant : codePostal, maxRows et pays.

IV. Côté serveur

Il faut maintenant faire le côté serveur, je vous propose donc de le faire dans deux langages différents. Le principe est quasiment pareil, choisissez celui que vous voulez.

IV-A. ASP.NET C#

Nous allons commencer par créer une classe que l'on nommera AutoCompletionCPVille et qui sera constituée de deux propriétés le code postal (string) et la ville (string), ainsi que son constructeur.

C#
Sélectionnez
public class AutoCompletionCPVille
{
    private string codePostal;
    private string ville;

    public AutoCompletionCPVille(string p_codePostal, string p_ville)
	{
        codePostal = p_codePostal;
        ville = p_ville;
	}

    public string CodePostal
    {
        get { return codePostal; }
        set { codePostal = value; }
    }

    public string Ville
    {
        get { return ville; }
        set { ville = value; }
    }
}

Ensuite, nous allons instancier une liste vide de la classe AutoCompletionCPVille. Cette liste sera remplie par la base de données puis elle sera convertie en JSON. Il faut ensuite construire la requête SQL en fonction de ce que l'on veut chercher, code postal ou ville. Il nous reste plus qu'à boucler sur nos résultats afin d'instancier un objet de la classe AutoCompletionCPVille, puis d'insérer cet objet dans notre liste.

Pour transformer notre liste d'objet de type AutoCompletionCPVille, nous allons utiliser la classe JavaScriptSerializer, présence depuis la version 3.5 du Framework .NET. Cette classe contient la méthode Serialize() qui prendra en paramètre notre liste et nous retournera une chaine de caractère correspondante au format JSON de notre liste.

C#
Sélectionnez
using System;
using System.Configuration;
using System.Collections.Generic;
using System.Web.Script.Serialization;
using MySql.Data.MySqlClient;

public partial class client_AutoCompletion : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //Initialisation de la liste
        List<AutoCompletionCPVille> list = new List<AutoCompletionCPVille>();

        //Connexion MySQL
        MySqlConnection connexion = new MySqlConnection(ConfigurationManager.ConnectionStrings["batifacConnectionMySql"].ConnectionString);
        connexion.Open();

        //Construction de la requete
        string strQuery = "SELECT CP, VILLE FROM autocomplete WHERE ";
        if (Request.Form["codePostal"] != null)
        {
            strQuery += "CP LIKE ?codePostal ";
        }
        else
        {
            strQuery += "VILLE LIKE ?ville ";
        }
        strQuery += "AND CODEPAYS = 'FR' ";
        //Limite
        if (Request.Form["maxRows"] != null)
        {
            strQuery += "LIMIT 0 , ?maxRows";
        }
        MySqlCommand query = new MySqlCommand(strQuery, connexion);
        if (Request.Form["codePostal"] != null)
        {
            query.Parameters.AddWithValue("?codePostal", Request.Form["codePostal"] + "%");
        }
        else if (Request.Form["ville"] != null)
        {
            query.Parameters.AddWithValue("?ville", Request.Form["ville"] + "%");
        }
        //Limite
        if (Request.Form["maxRows"] != null)
        {
            query.Parameters.AddWithValue("?maxRows", int.Parse(Request.Form["maxRows"]));
        }

        MySqlDataReader data = query.ExecuteReader();

        while (data.Read())
        {
            list.Add(new AutoCompletionCPVille(data.GetString("CP"), data.GetString("VILLE")));
        }

        //Fermeture du curseur
        data.Close();
        //Fermeture de la connexion
        connexion.Close();

        JavaScriptSerializer oSerializer = new JavaScriptSerializer();
        string strJSON = oSerializer.Serialize(list);
        Response.Write(strJSON);
    }
}

IV-B. PHP

Nous allons commencer par créer une classe que l'on nommera AutoCompletionCPVille et qui sera constituée de deux propriétés le code postal et la ville, ainsi que son constructeur. Les propriétés doivent avoir la visibilité public pour qu'elle puisse entre lu par la méthode de transformation en JSON.

PHP
Sélectionnez
<?php
class AutoCompletionCPVille {
	public $CodePostal;
	public $Ville;
}
?>

Ensuite, nous allons instancier un tableau vide. Ce tableau sera rempli par la base de données puis il sera converti en JSON. Il faut ensuite construire la requête SQL en fonction de ce que l'on veut chercher, code postal ou ville. Ensuite nous utiliserons la méthode fetchAll() avec les paramètres PDO::FETCH_CLASS et "AutoCompletionCPVille" pour insérer tous les résultats dans un tableau en gardant la structure de la classe AutoCompletionCPVille.

Pour transformer notre tableau d'objet de type AutoCompletionCPVille, nous allons utiliser la fonction json_encode, présence depuis la version 5.2 de PHP. Cette fonction nous retournera une chaine de caractère correspondante au format JSON de notre tableau.

PHP
Sélectionnez
<?php
require_once('./AutoCompletionCPVille.class.php');

//Initialisation de la liste
$list = array();

//Connexion MySQL
try
{
    $db = new PDO('mysql:host=localhost;dbname=bdname', 'root', 'root');
}
catch (Exception $ex)
{
    echo $ex->getMessage();
}

//Construction de la requete
$strQuery = "SELECT CP CodePostal, VILLE Ville FROM autocomplete WHERE ";
if (isset($_POST["codePostal"]))
{
    $strQuery .= "CP LIKE :codePostal ";
}
else
{
    $strQuery .= "VILLE LIKE :ville ";
}
$strQuery .= "AND CODEPAYS = 'FR' ";
//Limite
if (isset($_POST["maxRows"]))
{
    $strQuery .= "LIMIT 0, :maxRows";
}
$query = $db->prepare($strQuery);
if (isset($_POST["codePostal"]))
{
    $value = $_POST["codePostal"]."%";
    $query->bindParam(":codePostal", $value, PDO::PARAM_STR);
}
else
{
    $value = $_POST["ville"]."%";
    $query->bindParam(":ville", $value, PDO::PARAM_STR);
}
//Limite
if (isset($_POST["maxRows"]))
{
    $valueRows = intval($_POST["maxRows"]);
    $query->bindParam(":maxRows", $valueRows, PDO::PARAM_INT);
}

$query->execute();

$list = $query->fetchAll(PDO::FETCH_CLASS, "AutoCompletionCPVille");;

echo json_encode($list);
?>

V. Amélioration

Nous allons à présent procéder à une amélioration non vitale au fonctionnement, mais forte intéressante en matière de ressource serveur. Nous allons mettre en cache, coté client, les réponses des requêtes Ajax effectuées, pour qui si l'utilisateur venait à taper deux fois la même requête nous ne ferions pas une requête Ajax, mais nous utiliserons le cache.

Pour cela rien de bien compliquer, il faut créer une variable que l'on nommera cache. Puis à chaque succès d'une requête Ajax, nous ajouterons dans notre variable, en clé la requête saisie et en valeur la valeur JSON de retour.

Ensuite à chaque requête, nous regarderons s'il la requête saisie existe en clé de notre variable cache grâce au mot clé in. Si c'est le cas alors nous utiliserons la valeur du cache pour construire le tableau d'objet pour le plug-in d'autocomplétion. Et si elle n'est pas présente, nous ferons tout simplement la requête Ajax habituelle, qui je le rappelle sera enregistrée dans la variable cache.

JavaScript
Sélectionnez
var cache = {};
$(function ()
{
	$("#cp, #ville").autocomplete({
		source: function (request, response)
		{
			//Si la réponse est dans le cache
			if (('FR' + '-' + request.term) in cache)
			{
				response($.map(cache['FR' + '-' + request.term], function (item)
				{

					return {
						label: item.CodePostal + ", " + item.Ville,
						value: function ()
						{
							if ($(this).attr('id') == 'cp')
							{
								$('#ville').val(item.Ville);
								return item.CodePostal;
							}
							else
							{
								$('#cp').val(item.CodePostal);
								return item.Ville;
							}
						}
					}
				}));
			}
			//Sinon -> Requete Ajax
			else
			{
				var objData = {};
				if ($(this.element).attr('id') == 'cp')
				{
					objData = { codePostal: request.term, pays: 'FR', maxRows: 10 };
				}
				else
				{
					objData = { ville: request.term, pays: 'FR', maxRows: 10 };
				}
				$.ajax({
					url: "./AutoCompletion.php",
					dataType: "json",
					data: objData,
					type: 'POST',
					success: function (data)
					{
						//Ajout de reponse dans le cache
						cache[('FR' + '-' + request.term)] = data;
						response($.map(data, function (item)
						{

							return {
								label: item.CodePostal + ", " + item.Ville,
								value: function ()
								{
									if ($(this).attr('id') == 'cp')
									{
										$('#ville').val(item.Ville);
										return item.CodePostal;
									}
									else
									{
										$('#cp').val(item.CodePostal);
										return item.Ville;
									}
								}
							}
						}));
					}
				});
			}
		},
		minLength: 3,
		delay: 600
	});
});

VI. Conclusion

Voilà notre système d'autocomplétion est maintenant en place, vous pouvez bien entendu l'améliorer ou bien y rajouter des fonctionnalités, notamment la gestion de différent pays. On voit ici toute la puissance et l'intérêt de l'utilisation d'un Framework Javascript.

Pour les personnes qui n'ont pas réussi à faire fonctionner leur système en suivant ce tutoriel, je vous mets à disposition les fichiers sources complets.

Autocomplétion PHP - MySQL

Autocomplétion PHP - SQLite

Autocomplétion ASP.NET C# - MySQL