Create A Reusable Star Rating Widget using OOP : Frontend Interview

0 27
Avatar for codixir
3 years ago

Introduction

In this part of the series, we will apply the principles of object oriented programming (oop) to re-create the star-rating widget we saw in part 1 and make it reusable in the process.

This is a common frontend engineering interview question.

Part 1 of this series can be found here.

The widget we built in part 1 was not reusable. The widget we will build in this article should be flexible to be used as many times as one wants whether on the same page or inside any part of an application. All you need to have is an HTML div element, with a unique class attribute, that is going to serve as a container for the widget wherever it gets inserted.

To reach our goal, we will create a Javascript class from which widget instances will be created.

Setup

The setup will be the same as it was in the first article. We will use the JS Template from codesandbox.io. This template comes with a src folder that containsindex.js ,style.css files and an index.html file outside of src. This time we won't need to add any styles in the styles.css file.

HTML

The HTML part is very simple. Only one line needs to be changed. Go into the index.html file and replace the existing div with the following line.

<div class="stars-container1"></div>

This div will serve as a container for the stars widget we are going to create later.

Javascript

Since we are using object oriented programming to create the widget, let’s go ahead and create the skeleton of our class inside the index.js file.

class Stars {
  constructor() {}
  
  init() {}
  
  setRating() {}
}

This class named Stars and, currently, has empty constructor, init and setRating methods.

Constructor

The constructor will help us build unique widget instances. We will make it accept parameters that determine:

  • where the widget will be placed

  • the number of stars it will have

  • styles for each list item element which will serve as container for an individual star

The className , numOfStars and styleOptions parameters will have an empty string, 0 and an object literal with a margin attribute as default values respectively.

constructor(
    className = "",
    numOfStars = 0,
    styleOptions = { margin: "5px" }
  ) {
    this.isValid = false;
    this.numOfStars = numOfStars;
    this.stars = [];    
    this.styleOptions = styleOptions;
    this.starsContainer = document.querySelector(className);
​
    try {
      if (this.starsContainer) {
        this.className = className;
        this.isValid = true;
      } else {
        this.isValid = false;
        throw new Error(`${className} does not exist.`);
      }
    } catch (e) {
      console.log(e.message);
    }
​
    if (this.isValid && this.numOfStars > 0) {
      this.init();
    }
  }
​

In the above, you can see that we are adding several attributes on the this object. The variable this.isValid will serve as a flag to indicate the parameters that get passed into the constructor are valid. It is important to do validation at this stage to ensure proper values are passed into the constructor at the time the widget is being built.

We also need to ensure the className that gets passed into the constructor is a valid class name of an exiting element in the index.html file. This is important because that element will be the one on which we will mount the widget. In addition, if the user passed in 0 as the number of stars, there won't be any widget. Therefore, if we don’t have valid values for the className and numOfStars parameters, the value of this.isValid will be false and that will save us from invoking the init function unnecessarily.

We also have a this.stars variable that has an empty array as its value and this.starsContainer whose value is the returned value of querySelector. Both will play important roles shortly.

Exception handling

A try-catch block is added to to catch exceptions that could arise due to invalid classNames. Inside the try block, we check whether or not the value of this.starsContainer exists. If it does exist, we assign the className to this.className and the value true to this.isValid. If that is not the case, we assign the value false to this.isValid and throw an error using the built in Javascript Error constructor. You can pass into this Error constructor any message that you think will be properly express the issue. This is a good approach for creating generic errors.

When an error is thrown, our program execution will jump into the catch block. Inside the catch we can open an error modal to show the error message. But, for this article, I will just print the error message on the console.

Outside of the try-catch block, the last piece of code we write is a condition that checks if the value of this.isValid is true and the value of this.numOfStars is greater than 0. And, if that is the case, we can invoke theinit method.

Init

The init method is there to abstract away from the constructor logic we need to:

  • create an empty unorderd list (ul) element

  • generate list items and anchor elements

  • add star HTML entities to the anchor elements

  • add click event listeners to the anchor elements and implement the logic that will change the colours of the stars

  • append each anchor element to its respective list item element

  • append each list item element to the ul element we created

  • append the ul element to the the starsContainer element

init() {
    const ul = document.createElement("ul");
    for (let i = 0; i < this.numOfStars; i++) {
      this.stars.push({ id: i + 1 });
    }
    ul.style.listStyleType = "none";
    ul.style.display = "flex";
    const stars = this.stars.map((star) => {
      const li = document.createElement("li");
      const a = document.createElement("a");
      
      li.style.margin = this.styleOptions.margin;   
      a.style.cursor = "pointer";
      
      a.innerHTML = "&#9733";
      a.id = star.id;
      
      a.addEventListener("click", (e) => {
        this.setRating(ul, e);
      });
    li.appendChild(a);
      return li;
    });
    const fragment = document.createDocumentFragment();
    for (const star of stars) {
      fragment.appendChild(star);
    }
    ul.appendChild(fragment);
    this.starsContainer.appendChild(ul);
 }

You can see the ul element is created using the createElement method of the document object. We also have a for loop which lets us push into the this.stars array an object literal with an id attribute and a value equal to the current iteration variable for the loop. Once that is done, we apply styles to remove list item bullet points and horizontally align the list items we are going to create shortly.

Now, we have an array of objects stored inside the this.stars variable. We will use this array to generate li elements which in number will be equivalent to the value stored in this.numOfElements. To do that we need to invoke map on the this.stars array and transform each object that we have in the array into the a li element that wraps an anchor element with a star HTML entity.

Right away, inside the callback function, we create two elements li and a and apply some styles to both. In addition, we set an HTML entity which will appear as a star when rendered on the browser to the innerHTML attribute of the anchor element.

We also invoke the addEventListener method on the anchor element. This will help us listen to click events, whenever the element is clicked. The logic we apply inside the callback will help us change the colours of the stars. This logic will be implemented in the setRating method. The method will need ul and the event object as parameters to do its job.

Now all we need to do is append the anchor element to the list item element, which can be returned from the function.

This will give as an array of list items, stored in the stars constant. For performance, we create a document fragment and append to it each list item element that is in the stars array. We then append the fragment to the ul element, which in turn gets appended to the element stored in this.starsContainer.

Implement setRating

Inside the setRating method, we create the logic that will set the golden colour to the star that is clicked and all the sibling stars that are to its left. But, on every click, we need to reset the colours of all the stars.

setRating(ul, e) {
    const listItems = ul.querySelectorAll("li");
    const currentId = Number(e.target.id);
    for (const item of listItems) {
      const a = item.querySelector("a");
      a.style.color = "";
      if (a.id <= currentId) {
        a.style.color = "#ccac00";
      } else {
        a.style.color = "";
      }
    }
 }

The first thing we are doing inside this function is use the querySelectorAll method to get all the list item elements that are inside the ul element. This method returns a Javascript array containing the li elements. Now we can loop over the elements of the array and get access to the anchor element that is inside each element.

After getting the anchor element of the current item, using querySelector, we set an empty string to its style’s color attribute. This is a reset operation.

Then we check if the id of the current anchor element we are looping over is less than or equal to the id of the anchor element we just clicked on. We are able to get that id using the target attribute of the event object. If this condition is true, then we set the hex value of the golden colour to the color attribute of the style of the anchor element. Otherwise, the colour will have an empty string as its value.

Usage

Now, to create the widget, you can go outside of the class and create a new instance using the Stars constructor.

const stars1 = new Stars(".stars-container1", 5);

You can find the source code here.

Summary

We were able to create a reusable widget with the application of Object Oriented Programming (OOP) principles.

Checkout these TOP JAVASCRIPT courses on Udemy:

1 — The Complete JavaScript Course 2021: From Zero to Expert!

2 — JavaScript: Understanding the Weird Parts

3 — JavaScript — The Complete Guide 2021 (Beginner + Advanced)

4 — Modern Javascript From The Beginning

5 — The Modern JavaScript Bootcamp

Checkout my youtube channel for programming courses and tutorials:
https://www.youtube.com/channel/UCA5ZfHC6koHsukCLzCzxuGA

If you are looking for a Software development job, checkout HIRED.

4
$ 0.14
$ 0.14 from @TheRandomRewarder
Avatar for codixir
3 years ago

Comments