Movie Comparison App in JavaScript

Movie Comparison App in JavaScript
Code Snippet:Simple Movie Comparison App ( VanillaJS and Axios)
Author: Wesley van Drimmelen
Published: January 14, 2024
Last Updated: January 22, 2024
Downloads: 440
License: MIT
Edit Code online: View on CodePen
Read More

This JavaScript code is designed to create a web application that allows you to compare movies. It uses an autocomplete feature to search for movies, fetches movie data from OMDb API, and displays a comparison of key movie statistics. This app is helpful for comparing different movies based on factors like awards, box office earnings, Metascore, IMDB rating, and IMDB votes.

You can integrate this movie comparison app into your movies/entertainment website, allowing users to make informed decisions on what to watch next.

How to Create Movie Comparison App in JavaScript

1. First of all, load the Normalize CSS by adding the following CDN link into the head tag of your HTML document.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

2. After that, create the HTML structure for the movies comparison interface as follows:

<main>
  <header class="header">
    <h1 class="header__title">Movie Comparison</h1>
  </header>
  
  <div class="columns">
    <!--   Left side movie   -->
    <section class="column">
      <div id="left-autocomplete"></div>
      <div id="left-details"></div>
    </section>
    
    <!--   Right side movie   -->
    <section class="column">
      <div id="right-autocomplete"></div>
      <div id="right-details"></div>
    </section>
  </div>

</main>

3. Now, use the following CSS code to style the app interface. You can modify the CSS rules according to your website/app design.

* {
  box-sizing: border-box;
}

p {
  margin: 0;
}

body {
  margin: 0;
  padding: 0;
  color: #fff;
  background-color: #1D253D;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}

.columns {
  display: flex;
  max-width: 900px;
  margin: 0 auto;
}
.columns .column {
  flex: 0 0 50%;
  padding: 0 1rem;
}

.header {
  padding: 2rem 1rem;
  text-align: center;
}
.header__title {
  margin: 0;
  color: #6478B4;
  font-size: 20px;
  letter-spacing: 2px;
}

.autocomplete__label {
  display: block;
}
.autocomplete__input {
  display: block;
  width: 100%;
  margin-top: 0.5rem;
  padding: 1rem 1.25rem;
  border: 0;
  border-radius: 4px;
  color: #fff;
  background-color: #2d3a60;
  outline: 0;
  font-size: 18px;
  transition: all 0.15s ease-out;
   box-sizing: border-box;
}
.autocomplete__input:focus {
  box-shadow: 0 0 0 3px rgba(100, 120, 180, 0.75);
}
.autocomplete__input:hover {
  background-color: #364471;
}
.autocomplete .dropdown__menu__item img {
  width: 30px;
  margin-right: 1rem;
}

.dropdown {
  position: relative;
  vertical-align: top;
  display: inline-flex;
  width: 100%;
}
.dropdown--active .dropdown__menu {
  display: block;
}
.dropdown__menu {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 20;
  overflow-y: scroll;
  display: none;
  width: 100%;
  min-width: 12rem;
  max-height: 300px;
  padding: 0.5rem 0;
  border-radius: 4px;
  background-color: white;
}
.dropdown__menu__link {
  display: flex;
  align-items: center;
  padding: 0.5rem 1rem;
  color: #1D253D;
  cursor: pointer;
  transition: all 0.15s ease-out;
}
.dropdown__menu__link:hover, .dropdown__menu__link:focus {
  background-color: #eee;
}

.media {
  display: block;
  margin-bottom: 2rem;
}
.media__title {
  text-align: center;
}
.media__info {
  height: 175px;
  overflow-y: auto;
}
.media__figure {
  width: 100%;
  margin: 0;
  padding: 1rem;
  border-radius: 10px;
  background-color: #252f4e;
}
.media__figure img {
  display: block;
  margin: 0 auto;
  height: 200px;
  border-radius: 10px;
}

.compare {
  padding: 1.5rem 1rem;
  margin-bottom: 1rem;
  border-radius: 10px;
  color: #1D253D;
}
.compare--winner {
  background-color: #19CF7F;
}
.compare--loser {
  background-color: #F30C81;
}
.compare__title {
  margin-bottom: 0.5rem;
  font-weight: 700;
  font-size: 24px;
}
.compare--winner .compare__subtitle {
  color: #0e7447;
}
.compare--loser .compare__subtitle {
  color: #92074d;
}

4. Load the Axios JS library by adding the following scripts before closing the body tag:

<script src='https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js'></script>

5. Finally, add the following JavaScript code between <script> tag (or external JS file) to enable comparison functionality.

/**

  If the calls fail, it might be because the api limit has been reached.
  It's able to do 1.000 calls/day, search for 89c28a6e and replace it with your own (you can get one for free at http://omdbapi.com/)

*/

// Some default classes and variables used in the app
const DEFAULTS = {
  DROPDOWN_CLASS_ITEM: "dropdown__menu__item",
  DROPDOWN_CLASS_LINK: "dropdown__menu__link",
  DROPDOWN_CLASS_ACTIVE: "dropdown--active",
  COMPARE_CLASS_WINNER: "compare--winner",
  COMPARE_CLASS_LOSER: "compare--loser",
  API_BASE_URL: "https://www.omdbapi.com",
  API_KEY: "89c28a6e"
};

// Reusable Autocomplete Class
class Autocomplete {
  constructor(config) {
    ({
      root: this.root,
      optionTemplate: this.optionTemplate,
      onOptionSelect: this.onOptionSelect,
      inputValue: this.inputValue,
      fetchData: this.fetchData
    } = config);

    this.createRootTemplate(this.root);

    this.input = this.root.querySelector(".autocomplete__input");
    this.dropdown = this.root.querySelector(".autocomplete__dropdown");
    this.resultsWrapper = this.root.querySelector(".results");

    this.initListeners();
  }

  initListeners() {
    this.input.addEventListener("input", this.debounce(ev => this.onInput(ev), 500));

    document.addEventListener("click", ev => {
      if (!this.root.contains(ev.target)) {
        this.dropdown.classList.remove(DEFAULTS.DROPDOWN_CLASS_ACTIVE);
      }
    });
  }

  async onInput(ev) {
    const items = await this.fetchData(ev[0].target.value);

    if (!items.length) {
      this.dropdown.classList.remove(DEFAULTS.DROPDOWN_CLASS_ACTIVE);
      return;
    }

    this.resultsWrapper.innerHTML = "";
    this.dropdown.classList.add(DEFAULTS.DROPDOWN_CLASS_ACTIVE);

    for (const item of items) {
      const option = document.createElement("li");
      option.classList.add(DEFAULTS.DROPDOWN_CLASS_ITEM);

      const link = document.createElement("a");
      link.classList.add(DEFAULTS.DROPDOWN_CLASS_LINK);
      link.innerHTML = this.optionTemplate(item);

      option.appendChild(link);
      option.addEventListener("click", () => {
        this.dropdown.classList.remove(DEFAULTS.DROPDOWN_CLASS_ACTIVE);
        this.input.value = this.inputValue(item);
        this.onOptionSelect(item);
      });

      this.resultsWrapper.appendChild(option);
    }
  }

  debounce(callback, delay = 1000) {
    return (...args) => {
      if (this.timeoutId) clearTimeout(this.timeoutId);
  
      this.timeoutId = setTimeout(() => {
        callback.call(null, args);
      }, delay);
    };
  }

  createRootTemplate(el) {
    el.innerHTML = `
      <div class="autocomplete">
        <label class="autocomplete__label">
          Search
          <input class="autocomplete__input" />
        </label>

        <div class="autocomplete__dropdown dropdown">
          <ul class="dropdown__menu results">
          </ul>
        </div>
      </div>
    `;
  }
}

// Class to compare movies
class MovieComparison {
  constructor() {
    this.init();
  }

  movieAutocompleteConfig() {
    return {
      optionTemplate(movie) {
        const imgSrc = movie.Poster === "N/A" ? "" : movie.Poster;
        return `
        <img src="${imgSrc}" />
        ${movie.Title} (${movie.Year})
      `;
      },
      inputValue(movie) {
        return movie.Title;
      },
      async fetchData(searchTerm) {
        const response = await axios.get(DEFAULTS.API_BASE_URL, {
          params: {
            apikey: DEFAULTS.API_KEY, // This api can only be used 1000 times a day
            s: searchTerm
          }
        });

        return response.data.Error ? [] : response.data.Search;
      }
    };
  }

  init() {
    new Autocomplete({
      ...this.movieAutocompleteConfig(),
      root: document.querySelector("#left-autocomplete"),
      onOptionSelect: movie => {
        this.onMovieSelect(
          movie,
          document.querySelector("#left-details"),
          "left"
        );
      }
    });

    new Autocomplete({
      ...this.movieAutocompleteConfig(),
      root: document.querySelector("#right-autocomplete"),
      onOptionSelect: movie => {
        this.onMovieSelect(
          movie,
          document.querySelector("#right-details"),
          "right"
        );
      }
    });
  }

  async onMovieSelect(movie, summaryElement, side) {
    const response = await axios.get(DEFAULTS.API_BASE_URL, {
      params: {
        apikey: DEFAULTS.API_KEY,
        i: movie.imdbID
      }
    });

    summaryElement.innerHTML = this.movieTemplate(response.data);

    if (side === "left") {
      this.leftMovie = response.data;
    } else {
      this.rightMovie = response.data;
    }

    if (this.leftMovie && this.rightMovie) {
      this.runComparison();
    }
  }

  runComparison() {
    const leftSideStats = document.querySelectorAll("#left-details .compare");
    const rightSideStats = document.querySelectorAll("#right-details .compare");

    console.log(rightSideStats);

    for (const [i, leftStat] of leftSideStats.entries()) {
      const rightStat = rightSideStats[i];

      const leftSideValue = parseInt(leftStat.dataset.value);
      const rightStatValue = parseInt(rightStat.dataset.value);

      if (rightStatValue > leftSideValue) {
        leftStat.classList.remove(DEFAULTS.COMPARE_CLASS_WINNER);
        leftStat.classList.add(DEFAULTS.COMPARE_CLASS_LOSER);
      } else {
        rightStat.classList.remove(DEFAULTS.COMPARE_CLASS_WINNER);
        rightStat.classList.add(DEFAULTS.COMPARE_CLASS_LOSER);
      }
    }
  }

  movieTemplate(detail) {
    const boxOffice = parseInt(
      detail.BoxOffice.replace(/\$/g, "").replace(/,/g, "")
    );
    const metascore = parseInt(detail.Metascore);
    const imdbScore = parseFloat(detail.imdbRating);
    const imdbVotes = parseInt(detail.imdbVotes.replace(/,/g, ""));
    const awardsCount = detail.Awards.split(" ")
      .filter(Number)
      .reduce((acc, cur) => acc + parseInt(cur), 0);

    return `
      <article class="media">
        <h1 class="media__title">${detail.Title}</h1>
        <figure class="media__figure">
            <img src="${detail.Poster}" />
        </figure>
  
        <div class="media__info">
          <h4>${detail.Genre}</h4>
          <p>${detail.Plot}</p>
        </div>
      </article>
  
      <article class="compare ${DEFAULTS.COMPARE_CLASS_WINNER}" data-value="${awardsCount}">
        <p class="compare__title">${detail.Awards}</p>
        <p class="compare__subtitle">Awards</p>
      </article>
  
      <article class="compare ${DEFAULTS.COMPARE_CLASS_WINNER}" data-value="${boxOffice}">
        <p class="compare__title">${detail.BoxOffice}</p>
        <p class="compare__subtitle">Box Office</p>
      </article>
  
      <article class="compare ${DEFAULTS.COMPARE_CLASS_WINNER}" data-value="${metascore}">
        <p class="compare__title">${detail.Metascore}</p>
        <p class="compare__subtitle">Metascore</p>
      </article>
  
      <article class="compare ${DEFAULTS.COMPARE_CLASS_WINNER}"" data-value="${imdbScore}">
        <p class="compare__title">${detail.imdbRating}</p>
        <p class="compare__subtitle">IMDB Rating</p>
      </article>
  
      <article class="compare ${DEFAULTS.COMPARE_CLASS_WINNER}"" data-value="${imdbVotes}">
        <p class="compare__title">${detail.imdbVotes}</p>
        <p class="compare__subtitle">IMDB Votes</p>
      </article>
    `;
  }
}

const comparison = new MovieComparison();

If you plan to use the app regularly, you can get your API key from OMDb API for an improved experience. Replace the default API key in the code with your own.

That’s all! hopefully, you have successfully created a Movie Comparison App in JavaScript. If you have any questions or suggestions, feel free to comment below.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

About CodeHim

Free Web Design Code & Scripts - CodeHim is one of the BEST developer websites that provide web designers and developers with a simple way to preview and download a variety of free code & scripts. All codes published on CodeHim are open source, distributed under OSD-compliant license which grants all the rights to use, study, change and share the software in modified and unmodified form. Before publishing, we test and review each code snippet to avoid errors, but we cannot warrant the full correctness of all content. All trademarks, trade names, logos, and icons are the property of their respective owners... find out more...

Please Rel0ad/PressF5 this page if you can't click the download/preview link

X