Accueil Blog ⇉ Tuto système de notation par étoiles PHP - CSS - JS

php

css

javascript

illustration_article

Avant toute chose, ce modeste article n'a aucunement vocation à vous convaincre du bien-fondé de son objet (de très bonnes pages s'en chargent avec brio, cf la note en bas de ce tuto par exemple) mais la majorité des études récentes allant dans le sens très clair d'un apport positif chez Google et son référencement SEO, voici en quelques étapes simples, comment coder un système de notation par étoiles.

Loin de l'idée du plugin aux 15 000 options "qui fait tout sauf le café", ou du CSS superbe mais qui vient avec son tube d'aspirine, visons un système simple et qui se tient quand même : c'est le même système qui sert aux pages de ce blog, vous le voyez donc marcher avant même de commencer !

***********
J'ai ajouté la partie serveur, donc pour plus de clarté voici dans quel ordre c'est traité :
- Front-end : Affichage des étoiles
1/ JQuery pour mémoire (je ne m'en sers plus)
2/ JS
-> avec la réserve pour inclure l'appel Ajax
-> Appel Ajax avec la réserve pour inclure la réponse d'ajax
- Serveur :
1/ MySql
2/ PHP -> appel Ajax
- Front-End :
1/ PHP -> appel initial des données
2/ HTML -> Affichage des données
3/ JS : Gestion retour de l'appel ajax

(Ça fait un peu touffu mais je n'ai pas trouvé moins illogique)
***********


En deux mots : le principe est ultra-simple : on aligne 5 étoiles transparentes comme celle-ci

et on les place sur une div dont la particularité va être de s'allonger suivant la note obtenue, le reste c'est du code basique.

Comme prévu : Simplissime.


Commençons de suite par le HTML & CSS



 
/*code CSS */
.tde {height:20px;width:20px;cursor:pointer;}
#value {height:20px; width: <?=$value1;?>px; background:#E0E001;}
#glob {display: flex;}



$value1 ques aco me direz-vous ? En fait c'est tout simplement la transformation de la moyenne des notes entre 0 et 5 en pixels rapportée à la longueur de votre div #glob (en fonction de la taille des étoiles) ; et dans le système de ce blog, la moyenne c'est $value, alors bon... ;-)


<!--div optionnelle pour contenir le tout-->
<div style="float:right;margin-top : 10px;width:100px;">

<!--div en arrière-plan qui s'allongera en fonction de la valeur de $value1-->
<div id="value">

<!--div qui contient les étoiles-->
<div id="glob" >
<img id="tde_1" src="images/star.png" class="tde"/>
<img id="tde_2" src="images/star.png" class="tde"/>
<img id="tde_3" src="images/star.png" class="tde"/>
<img id="tde_4" src="images/star.png" class="tde"/>
<img id="tde_5" src="images/star.png" class="tde"/>
</div>
</div>
</div>



Quelques explications : Il est capital que la div au 2nd plan ne soit ni plus haute ni plus basse que la div qui contient les étoiles, et attention à la valeur de $value1 : basiquement, il faudra transformer la note obtenue de la base de donnée (entre 0 et 5 donc) en longueur en pixel, où 0 = 0px et où 5 = la longueur de #glob (ici 100px) ; notez au passage le "display: flex", de l'excellent Flexbox

Vous obtenez donc ça : ($value1 = 31)



help “ Les deux divs principales doivent impérativement être exactement superposées”



JQuery (pour l'exemple)



A ce stade, vos 5 étoiles sont alignées dans leur div, le tout sur la div de fond (si vous avez mis une valeur valide pour $value1, vous avez déjà obtenu un résultat ;-) mais rien ne bouge, passons donc au JQuery, également tout simple :


//on détecte la présence de la souris sur une étoile
$(".tde").mouseover(function(){

//Grâce à substring(), on récupère le numéro dans l'id de cette étoile et on la stocke dans une variable en ayant supprimé le préfixe "tde_", bonnes pratiques du HTML !
var nbr = $(this).prop('id').substring(4);

//on impose la couleur jaune dans le fond transparent de cette étoile
$(this).css( "backgroundColor", "#E0E001" );

//et en même temps, on met toutes les étoiles en-dessous de nbr en jaune.
$( ".tde").slice(0, nbr).css( "backgroundColor", "#E0E001" );

//et toutes celles au-dessus de nbr en gris
$( ".tde").slice(nbr).css( "backgroundColor", "#A1A1A1" );
})

//et quand la souris s'en va, on annule le fond jaune sous les étoiles pour garder uniquement celui de #value
$("#glob").mouseout(function(){
$(".tde").css('backgroundColor', "" );
})



Full Javascript


Voici la version pur JS, beaucoup plus dense, mais avec de nombreux avantages (et c'est celle qui est utilisée dans cet exemple) :

var ar = []; 
//on parcourt les étoiles
document.querySelectorAll(".tde").forEach(item => {

//on les met dans un tableau
ar.push(item);

// on leur ajoute un écouteur d'évènement, ici la souris au-dessus
item.addEventListener("mouseover", () => {

// on récupère sa valeur
let nbr = item.id.substring(4);

// on lui met un fond jaune
item.style.backgroundColor = "#E0E001";

// on place dans un tableau l'élément courant ET les éléments avant
let arM =(ar.slice(0, nbr));

// et dans un autre tableau ceux après
let arL = (ar.slice(nbr));
arM.forEach(m => {

// et on leur applique la nelle couleur de fond
document.getElementById(m.id).style.backgroundColor = "#E0E001";
});
arL.forEach(l => {

//idem pour le gris
document.getElementById(l.id).style.backgroundColor = "#A1A1A1";
});
});

// on ajoute un écouteur sur le container pour le départ de la souris
document.getElementById("glob").addEventListener("mouseout", () => {

// on parcourt le tableau du début
ar.forEach(a => {

// on réinitialise le fond original
document.getElementById(a.id).style.backgroundColor = "";
})
});
//L'appel Ajax sera à inclure ici
});



Et le tour est joué, au moins pour l'aspect, en quelques lignes simples.
Mirifique résultat :



Traitement I : envoi des données


L'idée générale (toute bête) : nous détectons un click() dans une .tde, on envoie sa valeur et un identifiant de la page via un appel ajax() pour updater une base de données qui renverra les infos à insérer dans le Rich Snippet (c'était quand même le but initial) ici sous cette forme par exemple :


"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "vM", //vote moyen
"reviewCount": "tV" //total des votes
},



C'est parti :

  // ajout évènement "click" **attention, le item est celui déclaré dans le code au-dessus => cf l'insertion précisée**
item.addEventListener("click", () => {

// un objet ajax
let xhr = new XMLHttpRequest();

// un FormData qui va transporter les données
let fData = new FormData();

// on y met la valeur du vote
fData.set("value", item.id.substring(4));

// la référence de la page concernée -> A adapter sur votre système
fData.set("ref", 11);

// le format de la réponse
xhr.responseType = "json";

// 'post' pour ne pas tenter les fraudeurs + l'url + requête asynchrone(true)
xhr.open("POST", "ajax/Setstars.php", true);

// si erreur : (ou onReadyStateChange)
xhr.onerror = function() {
alert("Un problème est survenu");
};

// au chargement de la réponse
xhr.onload = function(){



//on inclura la gestion de la réponse ici *****voir plus bas****


};

// on envoie le fData vers le serveur
xhr.send(fData);
});


Vous devez obtenir quelque chose comme ça dans la console, onglet fetch/xhr -> Headers :




dans l'onglet Payload :




et rien dans le Preview.


Le frontend est (presque) fini, passons maintenant côté serveur proprement dit.

Traitement II : PHP - MySql


Présupposés : Une base de données MySql ou MariaDb en route et donc un fichier PHP connecté pour stocker les données dans deux colonnes.
Si ces colonnes n'existent pas, il faut les créer dans la table.

ALTER TABLE `samples11`
ADD COLUMN `ratingMore` INT(10) NULL DEFAULT '0' AFTER `id`,
ADD COLUMN `ratingTotal` INT(10) NULL DEFAULT '0' AFTER `ratingMore`;


Attention, pensez bien à modifier les noms des colonnes après AFTER en fonction de vos tables et surtout à inclure une valeur à zéro par défaut sous peine de crash, vu que le premier vote devra prendre en compte une valeur existante.
Voilà, MySql c'est réglé vous pouvez l'oublier

Il ne nous reste plus que le PHP (le voici avec la fonction de la connexion à part pour plus de clarté),
ici un fichier "Setstars.php" dans un répertoire "ajax" (vous mettez ce que vous voulez où vous voulez mais il faut que le JS le trouve dans l'appel ajax)

//on crée une classe avec une connexion db à part donc
class Setstars
{
public $db;

public function __construct()
{
$this->db = self::DataB(); //l'objet pour la connexion
}


/**
/*la function principale qui est appelée
*/
public function main()
{
//var id de la page
$ref = $_POST['ref'];


// var si déjà voté ou pas - Par défaut, pas voté
$err = 1;

//2nde query qui retournera les résultats actualisés
$resultQuery = "SELECT ratingMore, ratingTotal from samples11 WHERE id = '" . $ref . "'";

//Validation du vote et insertion des données le cas échéant :
// si un cookie GBNetRating existe (sous cette forme :,xx ,yy, zz...)
if (isset($_COOKIE['GBNetRatin'])) {

//on le transforme en tableau
$cook = explode(',', $_COOKIE['GBNetRatin']);

//sinon
} else {

//on créé un tableau vide
$cook = array();
}

//si le cookie ne contient pas la réf de la page actuelle === n'a pas voté :
if (!in_array($ref, $cook)) {

//TOUJOURS sécuriser une requête qui contient des données venant de l'utilisateur
$dataToInsert = [
'ratingMore' => $_POST['value'],
'ref' => $ref
];

//Création de la query
$stmt = $this->db->prepare("UPDATE samples11 SET ratingMore = (ratingMore + :ratingMore)
,ratingTotal=last_insert_ID(ratingTotal + 1) WHERE id = :ref");

//on insère les données (tout se fait ici)
$stmt->execute($dataToInsert);

//On ajoute la réf de la page dans le tableau qu'on a obtenu plus haut du cookie
array_push($cook, $ref . ',');

//On écrase le cookie avec les nouvelles valeurs
setcookie('GBNetRatin', implode(',', $cook), time() + (86400 * 365), '/');
}

//sinon (a voté déjà)
else {
$err = 0;
}

//on exécute la requête pour récupérer les données (pas besoin de prepare)
$result = $this->db->query($resultQuery)->fetch();

//on retourne le tout au JavaScript sous la forme d'un tableau encodé en Json comme annoncé dans l'appel Ajax [$err,"total des Votes","total des notes ", note moyenne]
echo json_encode(array(
$err,
$result['ratingTotal'],
$result['ratingMore'],
round($result['ratingMore'] / $result['ratingTotal'], 1)));
}

//function pour retourner la Db
private function DataB()
{
try
{
$Bd = new PDO('mysql:host=127.0.0.1;dbname=nomBase;charset=utf8', 'root', '');
$Bd->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
}
catch (exception $e) {
die('Erreur : ' . $e->getMessage());
}
return $Bd;
}
}
$stars = new Setstars();
$stars->main();


Si tout s'est bien passé, dans l'onglet preview il doit y avoir quelque chose comme ça quand vous cliquez sur une étoile:




Traitement III : Retour au Front-End


Voilà pour le traitement côté serveur. Maintenant, revenons au traitement "live" dans le JS et le HTML,
il ne reste plus qu'à reprendre la 2nde requête pour la mettre dans la page, histoire d'afficher la note et les étoiles d'emblée (sinon, ce qu'on a fait ne sert à rien) :

/*********/
//requête générale vers la page
$resultQuery = "SELECT ratingMore, ratingTotal from samples11 WHERE id = '" . $ref . "'";

//Connexion base
$Bd = new PDO('mysql:host=127.0.0.1;dbname=nomBase;charset=utf8', 'root', '');
$Bd->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

//requête non préparée, on est toujours chez nous
$result = $Bd->query($resultQuery)->fetch();

//si aucun vote n'a été fait, on affiche zéro partout, ce qui nous évitera le malheureux crash d'une division par zéro
if ($result['ratingTotal'] == 0) {
$result['value'] = 0;
$result['value1'] = 0;

//sinon
} else {
//Les valeurs qui suivent sont à injecter directement dans le DOM ou via une template comme n'importe quelle variable
//on sort la moyenne obtenue limitée à une décimale
$result['value'] = round($result['ratingMore'] / $result['ratingTotal'], 1);

//on sort la valeur de la div jaune du fond (sous les étoiles) à arranger en fonction de votre design
$result['value1'] = ($result['value'] * 20);
}


/****************************************
Voici le DOM placé en-dessous des étoiles dans cet exemple
*****************************************/


<!--En ce qui concerne la longueur du fond jaune, il vaut mieux surcharger le CSS de #value pour éviter tout problème de cache-->
<div id="value" style="width: <?=$result['value1']?>px">
<!--[...]-->

<div id="contResults" class="resSp">
<span>Note: </span><span id="note"><?=$result['value']?></span>
&nbsp;&nbsp;&nbsp;
<span>Votes: </span><span id="votes"><?=$result['ratingTotal']?></span>
</div>
<span id="co" class="resSp13"></span>

<div style="margin-top: 25px">
<span class="resSp13">Rich Snippet :</span>
<pre>
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "<?=$result['value']?>",
"reviewCount": "<?=$result['ratingTotal']?>"
},
</pre>
</div>


et pour finir, la traitement "live" via la réponse du serveur :

// à insérer dans le retour ajax du Javascript

// attention, cette ligne est déjà dans le code JS plus haut !
xhr.onload = function () {
let r = this.response;

// s'il n'y a pas d'erreur
if(r[0] === 1) {

//on change sauvagement la couleur du texte via la div
co.style.color = "green";

// on prévient poliment que c'est fait
co.innerHTML = "Merci pour votre vote !";

//sinon
} else {
co.style.color = "orange";
co.innerHTML = "Vous avez déjà voté !";
}

//on insère les valeurs dans les span avec les valeurs du tableau correspondantes
document.getElementById("note").innerHTML = r[3];
document.getElementById("votes").innerHTML = r[1];
document.getElementById("value").style.width = `${(r[3] * 20)}px`; //longueur de "#value" suivant votre design

}; //et celle-ci est également dans le JS du haut !



et voici le résultat final :



J'espère que j'ai été assez clair et lisible, le code ci-dessus est strictement le même qui est employé sur le blog, donc si ça ne marche pas chez vous, privilégiez l'erreur ;-) ; dans tous les cas, si vous avez un doute ou une question, ou une suggestion n'hésitez pas à me contacter à https://gb-net.fr/contact.
Merci d'avoir suivi !

Note : pour l'intérêt de la notation par étoiles chez Google, on peut consulter cet article complet sur la question et très bien documenté : avis étoiles

Note2 : On m'a conseillé de faire la 1ère partie en CSS pur : c'est effectivement faisable mais dans la mesure où le CSS n'implémente rien pour récupérer les éléments précédant un élément donné, on se serait retrouvé avec une usine à gaz sans aucun intérêt par rapport au JS



Modifié le 15-05-2020
Article précédent


Oups, MySQL : Field ‘xx’ doesn’t have a default value
Article suivant


WordPress et sites dynamiques : après eux le déluge ?

Commentaires

 amandine  2024-02-23

top

Publier un commentaire :



capcha   



Raccourcis : php css html sql js img

Prévisualiser
GB-Net.fr 2024



fleche haut
Résultats de votre recherche pour :
reduce close