Creating the front end of a rating system with css and javascript
Updated: 05-Dec-2022 / Tags: Javascript and Local Storage / Views: 1800 - Author: George
Introduction
Hi everyone.
In this article we are going to create the front end of a rating system and store the ratings in the browser's local storage.
In this way if the user comes back to the page the ratings he made will be still here.
I have prepared a live demo on what we are going to code.
Live demo
Below i have three rating groups. Consider that every group is rating a product.
When you click on a star, the associated number of the star and the product's id will be stored in the browser's local storage.
Let's say that the first rating group belongs to a product with id=12, if you click on the third star, the data that will be stored
in the local storage will look like this, {rating: "3", product-id: "12"}.
So when the user comes back to the page, we will grab the data from the local storage and apply them to the product
with id=12 and display the rating.
Also the user can not vote twice as long the data exists in the local storage.
Now i want you to try it out. Rate our "products", and then reload the page and you will see that the ratings you made
are still here.
As i said the data will be stored in your browser's local storage, so to clear them click on the "Reset Ratings" button,
else every time you visit this page, your ratings will be here.
If you tried out the live demo, this is how the rating data looks like in the local storage.
Have this picture in your mind when you reach the javascript code section, it will help you understand the code better.
Now let's see how we do this. And let's start from the project's files.
Project's folder
We are gonna need three files.
- We need an index.html file to write the html code.
- We need a script.js file to write the javascript code.
- And a styles.css file for the css code.
The html code
Let's see how a rating group is structured.
We have a parent div with a class of "ratings-wrapper", which is the top element of our rating group.
Inside the "ratings-wrapper" we have another div element with a class
of "ratings", which has also a dataset attribute in which we set the product's id,
data-productid="39". We assume that every rating group is a product.
Inside the "ratings" element we have the stars inside span elements.
The span elements also have a dataset attribute named data-rating
and we set as value, the corresponding star's position. This means that when we click
on the fifth star we get the data-rating attribute with a value of five.
<div class="ratings-wrapper">
<div data-productid="39" class="ratings">
<span data-rating="5">★</span>
<span data-rating="4">★</span>
<span data-rating="3">★</span>
<span data-rating="2">★</span>
<span data-rating="1">★</span>
</div>
</div>
But as you can see the order of the span elements are reversed. The first span element is the fifth
star and the last span element is the first star.
We arranged the span elements in this way because we will reverse them in the css code to achieve the hover effect.
It will make sense when we get to the css file.
Now let's add two more rating groups as we had in the live demo.
The second rating group will belong to a product with a data-productid of "40", and the third product
will have a data-productid of "12".
<div class="ratings-wrapper">
<div data-productid="40" class="ratings">
<span data-rating="5">★</span>
<span data-rating="4">★</span>
<span data-rating="3">★</span>
<span data-rating="2">★</span>
<span data-rating="1">★</span>
</div>
</div>
<div class="ratings-wrapper">
<div data-productid="12" class="ratings">
<span data-rating="5">★</span>
<span data-rating="4">★</span>
<span data-rating="3">★</span>
<span data-rating="2">★</span>
<span data-rating="1">★</span>
</div>
</div>
And that is all the html code we need. If we load the index.html page in the browser we will see the three rating groups. But we have no styling yet, so lets write next the css code.
The css code
In the styles.css file we are going to have the following code.
.ratings-wrapper{
border: thin solid #999;
display: inline-block;
margin-bottom: 20px;
}
.ratings{
display: flex;
}
.ratings span{
cursor: pointer;
transition: color .2s;
font-size: 50px;
}
.ratings span:hover{
color: orange;
}
.ratings span:hover ~ span{
color: orange;
}
.ratings span[data-clicked]{
color: orange;
}
.ratings span[data-clicked] ~ span{
color: orange;
}
Explaining the css code
Let me explain the css code because some rules are going to work together with our JavaScript code.
-
In our first rule we are targeting the "ratings-wrapper" element. The important thing here is to set the display property to "inline-block", in line 3. This way the width of the rating group will be as wide as the content.
.ratings-wrapper{ border: thin solid #999; display: inline-block; margin-bottom: 20px; }
-
Next we are going to target the "ratings" element, and we are going to set the display property to "flex". This way there will be no space between that stars.
.ratings{ display: flex; }
-
Next we are going to target the span elements (stars). We are going to make them big, we are going to add a transition effect to have a smooth animation when we hover over them, and we are going to display the cursor as a pointer.
.ratings span{ cursor: pointer; transition: color .2s; font-size: 50px; }
-
Next we are going to set the hover effect.
In line 14 we are targeting the span element that we are currently hover on, and we are going to set the color to "orange".In line 17 we use the tilde (~) character to target every span element that comes after the span element that we are currently hover on, and we set also the color to "orange".
.ratings span:hover{ color: orange; } .ratings span:hover ~ span{ color: orange; }
Let's see what we have so far.
If you hover over the stars you will notice that this is not the effect that what we want. When we hover over a specific star we want to target the preceding stars and not the stars that comes after.
So let's fix this.
Remember that in the html code we had the span elements structured in a reverse order?
So let's reverse them also in the css code to fix the hover effect, and also to make the html code having sense.
So in line 8 we set the flex-direction property to row-reverse..ratings{ display: flex; flex-direction: row-reverse; }
Now if you hover over a star, you will see that the preceding stars are targeted, and not the ones that comes after.
Now lets move to the last two rules that we have in the stylesheet.
-
Now the last two rules are working together with the javascript code that we are going to write.
In the javascript code we are going to set a dataset attribute named data-clicked on every span element that we click on.So in line 20 we are targeting every span element that has a data-clicked attribute and set the color to orange.
.ratings span[data-clicked]{ color: orange; } .ratings span[data-clicked] ~ span{ color: orange; }
And in line 23 we are targeting every span element that precedes the clicked span element and set the color also to orange. This is similar to the rules that we wrote for the hover effect, only here we are targeting the clicked span element. This will make sense when we get to that point in the javascript code.
This is all the css code that we need, so lets now move to the javascript file.
The JavaScript code
Now let's see and explain the javascript code that we have in the script.js file.
let stars = document.querySelectorAll(".ratings span");
let products = document.querySelectorAll(".ratings");
let ratings = [];
for(let star of stars){
star.addEventListener("click", function(){
let children = star.parentElement.children;
for(let child of children){
if(child.getAttribute("data-clicked")){
return false;
}
}
this.setAttribute("data-clicked","true");
let rating = this.dataset.rating;
let productId = this.parentElement.dataset.productid;
let data = {
"rating": rating,
"product-id": productId,
}
ratings.push(data);
localStorage.setItem("rating", JSON.stringify(ratings));
});
}
if(localStorage.getItem("rating")){
ratings = JSON.parse(localStorage.getItem("rating"));
for(let rating of ratings){
for(let product of products){
if(product.dataset.productid == rating["product-id"]){
let reverse = Array.from(product.children).reverse();
let index = parseInt(rating["rating"]) - 1;
reverse[index].setAttribute("data-clicked", "true");
}
}
}
}
Explaining the JavaScript code
Let's break down and explain the javascript code.
-
In line 1, we are targeting all the span elements inside the "ratings" div element, and store them in the stars variable.
In line 2 we are targeting all the elements with a class of "ratings", and we store them in the products variable. Remember we referring to each rating group as product.
let stars = document.querySelectorAll(".ratings span"); let products = document.querySelectorAll(".ratings"); let ratings = [];
And in line 3 we create an empty array called ratings. We are going to store the ratings we make in the ratings array and insert them in the local storage.
-
In line 5 we loop through the stars and we are adding an "onclick" event-listener to each star in line 6, so every time we click on a star a function will run.
for(let star of stars){ star.addEventListener("click", function(){
-
Next in line 8, and inside the function we are gonna target the parent element of the clicked star, and then we are going to grab every child element, and store it in the children variable.
let children = star.parentElement.children; for(let child of children){ if(child.getAttribute("data-clicked")){ return false; } }
Next in line 9 we are going to loop through the children, and if we find a span element with a data-clicked attribute, we are going to return false to stop the function. This means that the product is already rated. We don't want to rate the products over and over again. By stopping the function here the code that follows will not be executed.
-
Now if there is no data-clicked attribute found in the above for loop, that means that we can rate the product so we are going to set the data-clicked attribute to the span element that we clicked on.
this.setAttribute("data-clicked","true");
-
Next we are gonna grab the rating value in line 16, and the productid in line 17.
And in line 18 we are going to create an object named data, that will hold the rating value and the productid.
let rating = this.dataset.rating; let productId = this.parentElement.dataset.productid; let data = { "rating": rating, "product-id": productId, }
-
Next in line 22, we are going to insert the data object in the empty ratings array that we have created in line 3.
And in line 22 we are stringify the ratings array and store it in the browser's local storage. The data that are stored in the local storage are in a key=>value pair format. In our case here the key is "rating", and the value is the ratings array. Also we can not store javascript objects in the local storage, that's why we turn them to a string with the stringify method.
ratings.push(data); localStorage.setItem("rating", JSON.stringify(ratings)); }); // closing the event-listener function } // closing the for of loop.
Now with the code so far we can rate a product and store our rating in the local storage, now lets see how to display those ratings when the user comes back to the page.
-
In the next code-block we are going to check if there are rating-data stored in the local storage. If so we are going to loop through them and apply them to the products that are rated.
if(localStorage.getItem("rating")){ ratings = JSON.parse(localStorage.getItem("rating")); for(let rating of ratings){ for(let product of products){ if(rating["product-id"] == product.dataset.productid){ let reverse = Array.from(product.children).reverse(); let index = parseInt(rating["rating"]) - 1; reverse[index].setAttribute("data-clicked", "true"); } } } }
- In line 27 we use an if statement to check if there are data in the local storage.
- If this is the case, we get the data from the local storage and store them in the ratings array, in line 28.
- Next we have to loop through the data from the local storage in line 29, and through all the products from the page in line 30. Remember we are referring to every rating group as a product.
- Next in line 31, we use the rating["product-id"] from the local storage to find the same product on the page.
- If there is a match, we reverse all span elements of the specific product, because the span elements are in a reverse order in the html, and so we can target the correct span element in line 32.
- Next in line 33 we create a variable named index, and we assign the rating value from the local storage. But because arrays in javascript are zero based, we have to say minus one - 1, to get the correct index.
-
And last we use the index variable on the reverse array to set the data-clicked
attribute to the span element. Now our css code takes over and changes the color to orange of the
specific span element, and all the span elements that are before it.
Also the data-clicked attribute prevent us from voting again the same product.
Summary
And that's it, this is the way that i came up with, on how to code the front-end of a rating system.
I hope i have explained it right, if you have any questions please leave a comment, i will be happy to reply.
Thanks for reading, i hope you find the article helpful. Also if you find any errors please leave a comment so i can update the page with the correct code.
Source code
You can download the source code and use it in any way you like.
Times downloaded: 233
Comment section
You can leave a comment, it will help me a lot.
Or you can just say hi. 😉