<DOCTYPE html>
<html>
<head>
<meta name="description" content="Star Rating Widget with debounce logic">
</head>
<body>
<div class="star-container star-container-1">
</div>
<button class="clearRatingBtn">Clear Rating</button>
</body>
</html>
body{
text-align: center;
}
.star-container{
display: flex;
justify-content: center;
align-items: center;
}
.star-container .star{
font-size: 30px;
cursor: pointer;
margin: 5px;
transform: scale(1);
transition: transform .3s;
}
button{
border: none;
outline: none;
padding: 10px;
border-radius: 4px;
margin: 20px;
}
button:hover{
cursor: pointer;
background-color: black;
color: #f9f9f9;
}
.star-container .star::before{
content: "\2606"
}
.star-container .selected::before{
content:"\2605";
color: gold
}
.star-container .selected{
transform: scale(1.3);
transition: transform .3s;
}
// debounce logic
Object.defineProperty(Object, 'debounce', {
value: function(callback, timeout){
let timeout_id;
return function(thisArg){
if(timeout_id){
clearTimeout(timeout_id)
}
timeout_id = setTimeout(callback.bind(this, thisArg), timeout);
}
},
enumerable: true,
writable: true,
configurable: true
})
class StarRating{
/* constructor function
@arg1: Rating Scale ie. number of stars
@arg2: Target container
*/
constructor(ratingScale, containerName){
this.scale = ratingScale
this.containerName = containerName
this.rating = 0
this.createStarsAndAppend()
let currentRatingContainer = document.createElement('div')
currentRatingContainer.setAttribute('class', 'current-rating')
currentRatingContainer.innerHTML = "No Ratings yet"
document.querySelector('body').appendChild(currentRatingContainer)
}
// clears rating
clearRating(){
console.log('clicked');
document.querySelectorAll('.' + this.containerName + " > .star").forEach(function(node){
node.classList.remove('selected')
})
this.rating = 0
document.querySelector('.current-rating').innerHTML = "No Ratings yet"
}
// main logic on every star click
starClick(thisArg){
let starIndex = this.attributes['id'].value.split('-')[0] // get the star index from id attribute
let stars = document.querySelectorAll('.' + thisArg.containerName + " > .star")
thisArg.rating = 0
/* Iterate over all star nodes
and select until clicked star + increase the rating
and unselect remaining stars
*/
stars.forEach(function(star, index){
if(index < starIndex){
stars[index].classList.add("selected")
thisArg.rating+=1
}
else{
stars[index].classList.remove("selected")
}
})
// display the current rating
document.querySelector('.current-rating').innerHTML = "Your current rating is: " + thisArg.rating
}
// Logic for intial creation of star nodes and appending to DOM
createStarsAndAppend(){
let starContainer;
try{
starContainer = document.querySelector('.' + this.containerName)
if(!starContainer){
throw new Error(`${this.container} class doesn't exist! `)
}
/* 1. Iterate until given number of stars and create star nodes
2. Attach the event listeners with DEBOUNCED CALLBACK FUNCTIONS
3. Append to the DOM
*/
for(let i = 1; i <= this.scale; i++){
let star = document.createElement('span')
star.setAttribute('class', 'star')
star.setAttribute('id', i+"-"+this.containerName)
let thisArg = this
let debouncedFn = Object.debounce(this.starClick, 200)
star.addEventListener('click', function(){
debouncedFn.call(this, thisArg)
})
starContainer.appendChild(star)
}
// Attaching event listener for clear rating debounced function
let debouncedFn = Object.debounce(this.clearRating, 200)
document.querySelector(".clearRatingBtn").addEventListener('click', debouncedFn.bind(this))
}
catch(err){
console.error(err.name + ": " + err.message);
}
}
}
// instantiate the class
new StarRating(5, 'star-container-1')
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.