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.
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.
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 :
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.
<!
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.
<script type
=
"text/javascript"
src
=
"http://code.jquery.com/jquery-1.5.1.min.js"
></script>
<script type
=
"text/javascript"
src
=
"http://ajax.microsoft.com/ajax/jquery.ui/1.8.10/jquery-ui.js"
></script>
Mais aussi le CSS pour jQuery UI
<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.
$(
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.
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 :'…'},{…}] où 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.
$.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.
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.
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
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
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.
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.