

Implementing a Star Rating component in Vanilla JS
source link: https://dev.to/lapstjup/implementing-a-star-rating-component-in-vanilla-js-20ob
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.


Implementing a Star Rating component in Vanilla JS
Jul 2 Originally published at blog.lakbychance.com
・4 min read
Star Rating/Review UI is a common sighting across different sites on the Internet.
Today, we will implement a simple star rating component using Vanilla JS.
We are going to use Revealing-module-pattern here and our main module API would look like so :-
const ratingModule = makeStarRating();
ratingModule
will expose two methods in the name of getStarComponent
and getRating
.
But before we go into the technical nitty-gritties of JS here, let's try to visualize how the HTML will look for this :-
<ul class="stcomp">
<li data-rating="1" class="star" ></li>
<li data-rating="2" class="star" ></li>
<li data-rating="3" class="star" ></li>
<li data-rating="4" class="star" ></li>
<li data-rating="5" class="star" ></li>
</ul>
We will not actually use HTML to make this but the DOM APIs. Still it's good to pre-visualize how we are going to identify the rating value from each element and that is via the data-rating
custom attribute which will be available to us as dataset.rating
when using DOM APIs. Also CSS isn't the focus of this article. Though, it will available in the final codepen implementation.
So let's start by making a basic skeleton in JS for now :-
const makeStarRating = function (noOfStars = 5) {
let rating = 0;
let starComponent;
function changeRating(newRating) {
rating = newRating;
}
function getStarComponent() {
if (!starComponent) {
// create Star Component
}
return starComponent;
}
function renderChanges(rating) {
// render UI changes as per rating passed
}
function getRating() {
return rating;
}
function onMouseClick(){
// click event handler
}
function onMouseOver(){
// mouseover event handler
}
function onMouseLeave(){
// mouseleave event handler
}
return { getRating, getStarComponent };
};
That's a skeleton alright !!!
So from the above you can see that we also have provided noOfStars
(with default value of 5) as argument to makeStarRating
which will be used by renderChanges(rating)
later on.
So we have to first create a star component and return it if it's not already present. Here is how we can do it by implementing getStarComponent()
:-
function getStarComponent() {
if (!starComponent) {
starComponent = document.createElement("ul");
starComponent.className = "stcomp";
for (let i = 0; i < noOfStars; i++) {
const li = document.createElement("li");
li.setAttribute("data-rating", i + 1);
li.className = "star";
starComponent.append(li);
}
starComponent.addEventListener("mouseover", onMouseOver);
starComponent.addEventListener("mouseleave", onMouseLeave);
starComponent.addEventListener("click", onMouseClick);
}
return starComponent;
}
Here we are basically creating an ul
element and appending to it li
, noOfStars
times. And setting the data-rating
attribute and className
property of each li
element. Finally adding the relevant code for registering event handlers. One important thing to notice is that we are making use of event delegation so that only our parent ul
has one event handler (for each relevant event) which can take care of events bubbling from child li
elements. The event bubbling is only beneficial for click and mouseover events. For mouseleave event we don't need it since we only want the rating
to get reflected once we leave the parent ul
container. And fun fact, mouseleave
doesn't bubble !!
Now let's see how renderChanges(rating)
will look like :-
function renderChanges(rating) {
for (let index = 0; index < rating; index++) {
starComponent.children[index].classList.add("star-filled");
}
for (let index = rating; index < noOfStars; index++) {
starComponent.children[index].classList.remove("star-filled");
}
}
The above is actually going to reflect our UI changes for the stars. We will have a class by the name of star-filled
to highlight a star.
Up to the rating
number, all the stars would be highlighted and after that all the stars will remain non-highlighted.
Now comes the part where our event handlers come into picture, the first one being, onMouseClick
:-
function onMouseClick(e) {
let star = e.target;
let isStar = star.classList.contains("star");
if (isStar) {
let { rating } = star.dataset;
rating = rating === getRating() ? 0 : rating;
changeRating(rating);
renderChanges(rating);
}
}
Above we first check whether the target which is clicked is a star or not. If it is, we get the rating
from the dataset
property. Now we compare it with existing rating
(via getRating()
) and if both are equal, reset the rating
to 0. Then we save this rating
and render the changes.
We also want a hoverable star highlight feature for our component. We can achieve that via the combination of mouseover
and mouseleave
like so :-
function onMouseOver(e) {
let isStar = e.target.classList.contains("star");
if (isStar) {
const { rating } = e.target.dataset;
renderChanges(rating);
}
}
function onMouseLeave(e) {
renderChanges(rating);
}
Here inside onMouseOver
, we just skip the check for rating
and saving rating
bit which we are earlier doing using changeRating(rating)
inside onMouseClick
. We only want to reflect these changes in the UI but not persist unless click action is performed.
And on mouseleave
, just render the changes with the current saved rating
(Bless you closures!!!).
And that's it for a simple implementation !!
We can use makeStarRating
each time to give us new modules and each of such modules can call their getStarComponent
to return the parent ul
which can be appended to other containers.
Below is a working implementation of the same with keyboard focusing capabilities as well. I didn't cover it since that could be an overkill for a simple implementation but can surely be looked into. Roving tabindex is the technique which I have used which you can learn from here.
I am open to any feedback you have regarding the writeup or implementation. That's how I learn :)
Recommend
-
19
Understanding client side routing by implementing a router in Vanilla JS When working with...
-
17
-
6
-
5
I don't know if you've all noticed but there have been a lot of star rating posts being published on Dev these past few days. Including an entry from me. The main p...
-
6
After my scalable star rating and my ONE star rating
-
6
Star Rating: An SVG Solution 17 Aug 2021 For websites and platforms that provide users with content or reviews, it’s important to include a star rating. Recently, I needed to implement a star rating component for a p...
-
4
Model Y Achieves 5-Star Overall Safety Rating from NHTSA The Tesla Team January 13, 2021
-
6
Acumatica Receives 5-Star Rating in the 2022 CRN Partner Program Guide The 2022 CRN Partner Program Guide released its list of vendors winning their 5-star rating, an...
-
5
☆ React Native Stock Star Rating 🟢 React Native Stock Star Rating component with no dependencies Light Weight Easy to use star rating. Equivalent to React Native Stock Component Use the...
-
4
This week I've been contributing to open source project Meetify. Feature My idea was that on the
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK