Dernière m.à.j. : 2018-06-17
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 est écrit avec du PHP mais il est traductible en ASP ...)
- d'une base de données MySQL avec une (ou des) table(s), dont la structure est 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 'demo_ajax_articles_liste' comme suit :
CREATE TABLE demo_ajax_articles_liste( id INT(5) NOT NULL auto_increment, fichier VARCHAR(255) NOT NULL, titre VARCHAR(255) NOT NULL, -- ceux qui auront voté (+) PRIMARY KEY(id) ) Engine = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; INSERT INTO demo_ajax_articles_liste(fichier, titre) VALUES ('article1.html', 'L''ornithorynque polyglotte'), ('faq_newslettux.html', 'Questions des utilisateurs NewsletTux'), ('Tuto_NewsletTux2.html', 'Installation de NewsletTux de A à Z'); -- ce qui donne cet aperçu : id fichier titre ----------------------------------------------------------------------------- 1 article1.html L''ornithorynque polyglotte 2 faq_newslettux.html Questions des utilisateurs NewsletTux 3 Tuto_NewsletTux2.html Installation de NewsletTux 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 (je mets arbitrairement la popularité de départ à 50%) :
CREATE TABLE demo_ajax_articles_votes( id INT(5) NOT NULL auto_increment, id_article INT(5) NOT NULL, -- 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) ) Engine = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; INSERT INTO demo_ajax_articles_votes(id_article, pourcent_depart) VALUES (1, 50), (2, 50), (3, 50);
A chaque vote, on rajoutera +1 au compteur correspondant (+ ou -) et on affichera, en fonction du nombre de départ, l'état de la popularité...
Code source du moteur de vote
On supposera dans cet article que la connexion à la base de données est réalisée dans un fichier indépendant : "mysql.php". Voici le contenu de ce fichier, à titre d'exemple. Vous pouvez prendre votre propre système de connexion bien sûr.
<?php $mysql_host = '127.0.0.1'; $mysql_port = 3306; $mysql_name = 'base_vote_ajax'; // nom de la BDD $mysql_uname = 'compte_sql'; $mysql_upasswd = 'mot_de_passe'; // connexion MySQL try { $connexion = new PDO('mysql:host='.$mysql_host.';port='.$mysql_port.';dbname='.$mysql_name, $mysql_uname, $mysql_upasswd,array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")); $connexion->SetAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); } catch(Exception $e) { echo 'Erreur : '.$e->getMessage().'<br />'; echo 'N° : '.$e->getCode(); exit(); } ?>
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) { // on suppose que la connexion base de données est faite dans un fichier à part require('mysql.php'); $query = 'UPDATE demo_ajax_articles_votes SET votes_{TYPE} = (votes_{TYPE} + 1), num_votes = (num_votes + 1) WHERE id_article = :id_article;'; // replace vote $query = str_replace('{TYPE}', $vote, $query); $requete_prepare_1=$connexion->prepare($query); $resultat = $requete_prepare_1->execute(array(':id_article' => $id_article)); if ($resultat) { // read and return new status return ReadVoteStatus($id_article, $verbose); } else { die($query.'<br />'); // à supprimer en production et à remplacer par votre système d'erreurs }; } /* * 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) { // on suppose que la connexion base de données est faite dans un fichier à part require('mysql.php'); $query = 'SELECT num_votes, votes_plus, votes_moins FROM demo_ajax_articles_votes WHERE id_article = :id_article;'; $requete_prepare_1=$connexion->prepare($query); $requete_prepare_1->execute(array(':id_article' => $id_article)); $lignes=$requete_prepare_1->fetch(PDO::FETCH_ASSOC); $nb_lignes = $requete_prepare_1->rowCount(); $answer = ''; if ($nb_lignes == 1) { // on retourne un texte arbitrairement sous la forme : nb de votes | nb + | nb - $answer = $lignes['num_votes'].'|'.$lignes['votes_plus'].'|'.$lignes['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> <!--[if IE 8 ]><html class="ie ie8" lang="fr"> <![endif]--> <!--[if (gte IE 9)|!(IE)]><!--><html lang="fr"> <!--<![endif]--> <head> <meta charset="utf-8"> <title>Un petit script de vote en Ajax et MySQL</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <script src="ajax_vote.js"></script> <style type="text/css"> table { border:1px solid #000; width:80%; } thead tr th { border:1px solid #000; padding:5px; background-color:#3272ad; color:#FFF; font-weight:bold; } tbody tr td { border:1px solid #000; padding:5px; } </style> </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 dans la table de test et la popularité commence arbitrairement à 50%.</p> <?php // on suppose que la connexion base de données est faite dans un fichier à part require('mysql.php'); // fonction pour afficher 1 ligne de tableau par article function DisplayArticle($article) { // preparation du score de pourcentage (popularité de l'article) /* 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... */ // les votes des gens $total_votes_gens = $article['num_votes']; // par exemple : 200 votes : 170 + et 30 - $total_votes_gens_plus = $article['votes_plus']; // 170 $total_votes_gens_moins = $article['votes_moins']; // 30 // calcul de la popularité $popularite = ($total_votes_gens == 0 ) ? $article['pourcent_depart'] : intval(($total_votes_gens_plus / $total_votes_gens) * 100); // maintenant on affiche la ligne de l'article echo ' <tr> <td><strong>'.$article['titre'].'</strong></td> <!-- titre de cet article --> <td>[ <a href="http://monsite/'.$article['fichier'].'">Lien</a> ]</td> <!-- lien --> <td>Nombre de votes : <span id="numvotes_'.$article['id'].'">'.$article['num_votes'].'</span> </td> <!-- Nb de votes. Laissez bien un espace aprus le /span --> <td><span id="show_'.$article['id'].'" style="display:none;"> <!-- Si Javascript est désactivé, ces liens ne seront pas visibles (vote sans ajax) --> <!-- vote + --> <span id="vote_plus_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'plus\'); return false;" title="Voter (+)">+</a>)</span> <!-- vote - --> <span id="vote_moins_'.$article['id'].'">(<a href="#" onclick="vote(\''.$article['id'].'\',\'moins\'); return false;" title="Voter (-)">-</a>)</span></span> <!-- les liens de vote "classiques" (sans javascript), masqués si Javascript actif --> <span id="hide_'.$article['id'].'"> <!-- vote + --> (<a href="?act=vote&id_article='.$article['id'].'&vote=plus" title="Voter (+)">+</a>) <!-- vote - --> (<a href="?act=vote&id_article='.$article['id'].'&vote=moins" title="Voter (-)">-</a>) </span> </td> <td> <div style="padding-left: 20%; padding-right: 20%; margin-top: 1ex;"> <div style="border: 1px solid #000; padding: 1px; font-size: 8pt; height: 12pt; background-color: #FFF; position: relative;"> <div style="padding-top: 1pt; width: 100%; z-index: 2; color: #F60; position: absolute; text-align: center; font-weight: bold;">'.$popularite.' %</div> <div style="width:'.$popularite.'%; height: 12pt; z-index: 1; background-color: #009;"> </div> </div> </div> <!-- Si javascript inactif : les liens AJAX n\'apparaissent pas, les liens classique oui (vote sans Ajax) sinon, on masque les liens classiques et on active les liens AJAX --> <script>document.getElementById(\'hide_'.$article['id'].'\').style.display="none"; document.getElementById(\'show_'.$article['id'].'\').style.display="";</script> </td> </tr>'.PHP_EOL; } // récupération des articles depuis la base $array_articles = array(); $i = 0; $query = 'SELECT id, titre, fichier FROM demo_ajax_articles_liste;'; // A vous de mettre les where, order by etc. si nécessaire $requete_prepare_1 = $connexion->prepare($query); $requete_prepare_1->execute(array()); $nb_lignes = $requete_prepare_1->rowCount(); $i = 0; if ($nb_lignes > 0) { while($art = $requete_prepare_1->fetch(PDO::FETCH_ASSOC)) { $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 $query2 = 'SELECT num_votes, votes_plus, votes_moins, pourcent_depart FROM demo_ajax_articles_votes WHERE id_article = :id_article;'; $requete_prepare_2 = $connexion->prepare($query2); $requete_prepare_2->execute(array( ':id_article' => $art['id'])); $nb_lignes2 = $requete_prepare_2->rowCount(); if ($nb_lignes2 == 1) // il y a bien un vote pour cet article { while($vote = $requete_prepare_2->fetch(PDO::FETCH_ASSOC)) { $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); echo ' <table style="border:1px solid #000;"> <thead> <tr> <th>Titre article</td> <th>Lien d\'accès</td> <th>Nb. Votes</td> <th>Voter</td> <th>Popularité</td> </tr> </thead> <tbody>'.PHP_EOL; foreach($array_articles as $i => $article) { DisplayArticle($article); } echo ' </tbody> </table>'.PHP_EOL; // Vote sans javascript : même principe, mais "en local sur cette page". if ((isset($_GET['act'])) && ($_GET['act'] == 'vote')) { $dont_exec = true; require_once('ajax_vote.php'); // cf. plus haut dans cette page, 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 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 traiter les erreurs SQL : 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_once" 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 !