Un petit script de vote en AJAX et MySQL
Sommaire
Explications et prérequis
Explications
Le but de cet article est de faire un vote ultra simple qui n'a que 2 choix possibles : "oui" et "non". Une barre de progression montrera l'état des votes en cours ...
Pour faire plus "actuel", on utilisera dans cet article de l'AJAX (Asynchronous Javascript And XML), c'est une sorte d'interface entre le PHP et le Javascript, via de l'XML. Pourquoi ?
PHP est côté serveur (et c'est PHP qui va lire/écrire dans la base de données) et HTML/Javascript sont du coté client : pour actualiser les données, il n'y a donc pas d'autre alternative, à première vue, que de recharger toute la page ... Sauf si, dans un coin de page, un module dynamique (Javascript est, après tout, le côté dynamique chez le "client") permet d'aller à la volée lire des informations et les mettre à jour ...
Ce script présente donc un inconvénient (soyons clairs) c'est que sans javascript, il ne fonctionnera pas. Pour cela, on peut prévoir une variante ("accessibilité" oblige) qui ne nécessite pas le Javascript (mais dans ce cas, il y a rechargement complet de la page).
Prérequis
On a besoin :
- du langage PHP (ou un équivalent côté serveur, cet article eest écrit avec du PHP mais il est traductible en ASP ...)
- d'une base de données MySQL avec une (ou des) table(s), structure donnée plus loin.
- d'une page en HTML censée présenter les données
- d'une image (rouge.png dans mon exemple) qui représentera la barre de progression
- d'un peu de temps ...
Le code source
Structure de la base de données
Commençons par la base de données. Je suppose ici qu'on veut évaluer des articles qui ont un identifiant (ID) : savoir si vos visiteurs ont apprécié cet article. Il y a donc une table 'articles' comme suit :
id fichier titre ----------------------------------------------------------------------------- 1 article1.html L'ornithorynque polyglotte 2 faq_newslettux.html Questions des utilisateurs NewsletTux 3 Tuto_NewsletTux2.html Installation de NewsletTux 2 de A à Z
Bien entendu, le principe peut être adapté à n'importe quoi... L'idée maintenant est de mettre une sorte de notation pour chaque ID de cette table. Par souci de simplicité (et de clarté) dans ce script, on ne bloque pas à 1 vote par visiteur et par jour.
Voici la table qu'il faut créer pour stocker les votes :
CREATE TABLE votes_articles( id INT(5) NOT NULL auto_increment, id_article INT(5) NOT NULL, -- l'ID de l'article auquel ce vote se réfère num_votes INT(5) NOT NULL DEFAULT 0, -- le nombre de votants votes_plus INT(5) NOT NULL DEFAULT 0, -- ceux qui auront voté (+) votes_moins INT(5) NOT NULL DEFAULT 0, -- ceux qui auront voté (-) pourcent_depart INT(3) NOT NULL DEFAULT 0, -- le % de la barre au départ PRIMARY KEY(id) ) Type = MyISAM;
A chaque vote, on rajoutera +1 au compteur correspondant (+ ou -) et on affichera, en fonction du nombre de départ, l'état des votes...
Code source du moteur de vote
On va maintenant créer les fonctions qui vont réaliser les votes. Ce seront des fonctions, parce qu'on pourra les appeler plus facilement. Ces fonctions sont à placer dans un fichier d'extension .php, ce fichier devra être inclus dans la page des votes via un include (ou un require). Nommons par exemple ce fichier "ajax_vote.php" :
<?php
/*
* Cette fonction enregistre un nouveau vote pour l'article
* entrée : $id_article {int}
* entrée : $vote, ('plus' || 'moins') {string}
* entrée : $verbose, pour renvoyer le résultat {bool}
*
* sortie {string}
*/
function AddNewVote($id_article, $vote, $verbose = true)
{
require('mysql.php');
$req_update_votes = "UPDATE
vote_articles
SET
votes_{TYPE} = (votes_{TYPE} + 1),
num_votes = (num_votes + 1)
WHERE
id_article = '".$id_article."';";
// replace vote
$req_update_votes = str_replace('{TYPE}', $vote, $req_update_votes);
if (mysql_query($req_update_votes))
{
// read and return new status
return ReadVoteStatus($id_article, $verbose);
}
else
{
die($req_update_votes.'<br />'.mysql_error());
};
};
/*
* Cette fonction lit l'état d'un vote pour l'article
* entrée : $id_article {int}
* entrée : $verbose pour renvoyer le résultat {bool}
*
* sortie : {string}
*/
function ReadVoteStatus($id_article, $verbose)
{
require('mysql.php');
$req_vote_status = "SELECT
num_votes,
votes_plus,
votes_moins
FROM
vote_articles
WHERE
id_article = '".$id_article."';";
$vote_status = mysql_query($req_vote_status) or die ($req_vote_status.'<br />'.mysql_error());
$answer = '';
if (mysql_num_rows($vote_status) == 1)
{
$status = mysql_fetch_array($vote_status);
$answer = $status['num_votes'].'|'.$status['votes_plus'].'|'.$status['votes_moins'];
};
if ($verbose)
return $answer;
};
// maintenant on appelle ces fonctions
$dont_exec = (isset($dont_exec)) ? $dont_exec : false;
if (!$dont_exec) // evite la double exécution sans Javascript
{
$id_article = (isset($_GET['id_article'])) ? abs(intval($_GET['id_article'])) : 0;
$vote = (isset($_GET['vote'])) ? $_GET['vote'] : 'plus';
if (($vote != 'plus') && ($vote != 'moins')) { $vote = 'plus'; }
if ($id_article != 0)
{
echo AddNewVote($id_article, $vote, true); // répond par le statut du vote
};
};
?>
2 fonctions sont ici décortiquées (et nommées explicitement) : une fonction pour réaliser un vote (en plus ou en moins) et une fonction pour lire l'état des votes pour un article. Une fois qu'un vote est enregistré, on lit le nouvel état des votes de l'article : soit l'ID de l'article est complètement fantaisiste auquel cas on ne renvoie rien (une chaine vide, plus exactement), soit l'ID est bien réel et on renvoie 3 nombres, séparés par des pipes (AltGr + 6) qui sont respectivement le nouveau nombre de votes, le nouveau nombre de plus et le nouveau nombre de moins.
Il reste maintenant à faire 3 choses : créer les liens "plus" et "moins", créer les fonctions AJAX qui les activent (et les liens HTML en cas de Javascript indisponible) et afficher la barre de progression ;o)
Code source HTML : présentation des données
Cet exemple, simple, n'a pas la prétention de faire joli. On aura recours aux styles CSS pour embellir la présentation qui, je l'avoue, est très sommaire ici. Je suppose que par une requête on lit tous les articles, et pour chacun, on crée une ligne de paragraphe.
Ceci est la page articles.php, d'extension .php, qui présente les articles (et permet le vote d'ailleurs).
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>PHP-Astux - Accueil du site</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
<script type="text/javascript" src="ajax_vote.js"></script>
</head>
<body>
<h1>PHP-Astux, le site des scripts PHP !</h1>
<p>Cette page est celle qui liste mes articles pour lesquels j'organise un vote. Il y a 3 articles (les mêmes que ceux de l'article) et le vote commence toujours à 50%.</p>
<?php
require('mysql.php');
// display article
function DisplayArticle($article)
{
echo '<div>';
echo '<strong>'.$article['titre'].'</strong>'; // le titre de l'article
echo ' - [ <a href="http://monsite/'.$article['fichier'].'">Lien</a> ]'; // le lien vers l'article
// et maintenant, le système de vote
// Affichage du nombre total de votes
echo ' Nombre de votes : <span id="numvotes_'.$article['id'].'">'.$article['num_votes'].'</span> '; // laisser l'espace final
// les liens de vote en AJAX, par défaut masqués (et activés si Javascript est présent)
echo '<span id="show_'.$article['id'].'" style="display:none;">';
// vote +
echo '<span id="vote_plus_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'plus\'); return false;" title="Voter (+)">+</a>)</span> '; // laisser l'espace final
// vote -
echo '<span id="vote_moins_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'moins\'); return false;" title="Voter (-)">-</a>)</span></span> '; // laisser l'espace final
// les liens de vote "classiques" (sans javascript), masqués si Javascript actif
echo '<span id="hide_'.$article['id'].'">';
// vote +
echo '(<a href="?act=vote&id_article='.$article['id'].'&vote=plus" title="Voter (+)">+</a>) ';
// vote -
echo '(<a href="?act=vote&id_article='.$article['id'].'&vote=moins" title="Voter (-)">-</a>)';
echo '</span>';
// barre de progression : un <div> de largeur fixée, avec une couleur de fond et une image de largeur variable
//calcul du pourcentage (largeur de la barre rouge)
// on part du chiffre initial ($article['pourcent_depart']) et on lui ajoute les votes + et - :
// La note va de 0 à 100% et on part d'un nombre initial : X
// depuis ce nombre, il y a eu A votes (+) et B votes (-)
// on met tout le monde à la même échelle, et on recalcule la note...
// au départ
$total_votes_ini = 100;
$total_votes_ini_plus = $article['pourcent_depart'];
$total_votes_ini_moins = $total_votes_ini - $article['pourcent_depart'];
// les votes des gens
$total_votes_gens = $article['num_votes'];
$total_votes_gens_plus = $article['votes_plus'];
$total_votes_gens_moins = $article['votes_moins'];
// calcul des sommes
$total_final_votes = $total_votes_ini + $total_votes_gens;
$total_final_plus = $total_votes_ini_plus + $total_votes_gens_plus;
$total_final_moins = $total_votes_ini_moins + $total_votes_gens_moins;
// recalcul des % (avec antibug pour la division par 0) : on a la taille de l'image ;o)
$pourcent_votes_plus = ($total_final_votes == 0 ) ? 0 : intval(($total_final_plus / $total_final_votes) * 100);
echo '<div style="width:100px; background-color:#090;"><img src="rouge.png" width="'.$pourcent_votes_plus.'" height="20" alt="" style="border:0; margin:0; padding:0;"> </div>';
// Si javascript inactig : les liens AJAX n'apparaissent pas, les liens classique le sont
// sinon, on masque les liens classiques et on active les liens AJAX
echo '<script type="text/javascript">document.getElementById(\'hide_'.$article['id'].'\').style.display="none"; document.getElementById(\'show_'.$article['id'].'\').style.display="";</script>';
echo '</div>';
};
// récupération des articles depuis la base
$array_articles = array(); $i = 0;
$req_articles = "SELECT id, titre, fichier FROM articles;";
$articles = mysql_query($req_articles) or die($req_articles.'<br />'.mysql_error());
if (mysql_num_rows($articles) > 0)
{
while($art = mysql_fetch_array($articles))
{
$array_articles[$i]['id'] = $art['id'];
$array_articles[$i]['titre'] = $art['titre'];
$array_articles[$i]['fichier'] = $art['fichier'];
// ces variables seront mises à jour s'il y a un vote
$array_articles[$i]['num_votes'] = 0;
$array_articles[$i]['votes_plus'] = 0;
$array_articles[$i]['votes_moins'] = 0;
$array_articles[$i]['pourcent_depart'] = 0;
// on lit les votes pour cet article
$req_vote = "SELECT
num_votes,
votes_plus,
votes_moins,
pourcent_depart
FROM
vote_articles
WHERE
id_article = '".$art['id']."';";
$rs_vote = mysql_query($req_vote) or die ($req_vote.'<br />'.mysql_error());
if (mysql_num_rows($rs_vote) == 1)
{
$vote = mysql_fetch_array($rs_vote);
$array_articles[$i]['num_votes'] = $vote['num_votes'];
$array_articles[$i]['votes_plus'] = $vote['votes_plus'];
$array_articles[$i]['votes_moins'] = $vote['votes_moins'];
$array_articles[$i]['pourcent_depart'] = $vote['pourcent_depart'];
};
$i++;
};
};
// maintenant, on affiche les articles
//print_r($array_articles);
foreach($array_articles as $i => $article)
{
DisplayArticle($article);
};
// Vote sans javascript : même principe, mais "en local sur cette page".
if ((isset($_GET['act'])) && ($_GET['act'] == 'vote'))
{
$dont_exec = true;
require('ajax_vote.php'); // pour hériter des fonctions
$id_article = (isset($_GET['id_article'])) ? abs(intval($_GET['id_article'])) : 0;
$vote = (isset($_GET['vote'])) ? $_GET['vote'] : 'plus';
if (($vote != 'plus') && ($vote != 'moins')) { $vote = 'plus'; }
if ($id_article != 0)
{
if (AddNewVote($id_article, $vote, false) === NULL)
{
echo '<p>Votre vote a bien été pris en compte, merci ! <a href="articles.php">Cliquez sur ce lien pour revenir aux articles</a></p>';
}
};
};
?>
</body>
</html>
Code source de l'AJAX
L'AJAX est du javascript : c'est donc une suite d'instructions que nous allons placer dans un fichier, par exemple ajax_vote.js d'extension .js et appelé entre <head> et </head> par la ligne :
<script type="text/javascript" src="ajax_vote.js"></script>
Dans ce fichier, il y a une fonction qui crée un objet nommé XMLHTTPRequest. C'est l'objet XML qui va s'interposer entre le Javascript et le PHP. Il peut avoir 3 états : opération faite, opération en cours, opération finie. Les commentaires du fichier JS détaillent mieux tout cela.
function vote(id_article, type_vote)
{
// l'URL appelée pour voter
var url = 'ajax_vote.php';
var httpRequest = false;
if (window.XMLHttpRequest)
{ // Mozilla, Safari,...
httpRequest = new XMLHttpRequest();
if (httpRequest.overrideMimeType)
{
httpRequest.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject)
{ // IE
try
{
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {}
}
}
if (!httpRequest)
{
alert('Abandon :( Impossible de créer une instance XMLHTTP');
return false;
}
httpRequest.onreadystatechange = function() { alertContents(httpRequest, id_article); };
httpRequest.open('GET', url + '?id_article=' + id_article + '&vote=' + type_vote, true);
httpRequest.send(null);
return true;
}
function alertContents(httpRequest, id_article)
{
if (httpRequest.readyState == 4)
{
if (httpRequest.status == 200)
{
// Edit page content
str = httpRequest.responseText;
var tab = str.split('|');
// On lit (et on parse) la réponse : 1er nombre = nombre total de votes, puis Plus, puis Moins
var total_votes = tab[0];
var total_votes_plus = tab[1];
var total_votes_moins = tab[2];
// on met à jour la page de présentation, maintenant
document.getElementById('numvotes_' + id_article).innerHTML = total_votes;
// on efface les liens de vote
document.getElementById('vote_plus_' + id_article).innerHTML = '';
document.getElementById('vote_moins_' + id_article).innerHTML = '';
}
else
{
alert('Un problème est survenu avec la requête.');
}
}
}
Une démo ?
Point de vue sécurisation, il faut enlever (sur un serveur de production) tous les "die mysql_error" : on n'affiche pas les erreurs aux gens, on les traite... Je suppose pour cet article que vous avez bien entendu de quoi les masquer ou les traiter. Attention, il ne faut pas masquer une erreur pour le plaisir des yeux : il faut absolument la corriger...
Même si ce n'est pas explicitement écrit, dans les pages PHP vous avez fait un "require" sur un fichier contenant une connexion à MySQL : 4 identifiants, une ouverture de connexion.
Point de vue améliorations, on citera d'emblée (et sans hésiter) l'apparence. Oui, la démo est très sommaire mais il s'agit de montrer ce que ça donne en ligne, le reste c'est vous qui l'adaptez à vos besoins !

