How to make Country Search Main using HTML, JS

make Country Search

Creating a country search feature using HTML and JavaScript involves creating a simple user interface where users can input a search term to find a country. Here’s a step-by-step guide to building this feature:

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Country Search & Filter App</title>
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0"
    />
    <link rel="stylesheet" href="style.css" />
    <script src="script.js" defer></script>
  </head>
  <body>
    <div class="container">
      <!-- search -->
      <div class="search">
        <input type="search" id="search" placeholder="Search country" />
      </div>

      <!-- filter -->
      <div class="filter">
        <button>All</button>
        <div class="options"></div>
      </div>

      <!-- grid -->
      <div class="grid-container"></div>

      <!-- result -->
      <div class="no-result">No result found.</div>

      <!-- modal -->
      <div class="modal">
        <div class="modal-content">
          <button data-dismiss>
            <span class="material-symbols-outlined">close</span>
          </button>
          <figure>
            <img
              src="https://placehold.co/600x400/purple/white?text=FLAG"
              alt=""
            />
            <figcaption>Country Name</figcaption>
          </figure>
        </div>
      </div>
    </div>
  </body>
</html>

 

CSS (styles.css)

@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap");

:root {
  --color0: goldenrod;
  --color1: #082032;
  --color2: #2c394b;
  --color3: #9db2bf;
  --color4: #dde6ed;
}

* {
  font-family: "Poppins", sans-serif;
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

body {
  background: var(--color1);
  color: var(--color3);
}

img {
  width: 100%;
  height: auto;
  display: block;
}

/* layout */

.container {
  width: 90%;
  max-width: 1200px;
  margin: 2rem auto 1rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2rem;
}

.search,
.filter {
  flex-basis: calc(50% - 1rem);
}

.grid-container {
  flex-basis: 100%;
}

/* search */

.search input {
  width: 100%;
  max-width: 400px;
  padding: 10px;
  padding-left: 20px;
  border: none;
  border-radius: 5px;
  background: var(--color2);
  color: var(--color4);
  outline-color: var(--color0);
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25);
}

.search input::placeholder {
  color: var(--color3);
}

.search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-left: 10px;
  background: linear-gradient(
      45deg,
      rgba(0, 0, 0, 0) 0%,
      rgba(0, 0, 0, 0) 43%,
      var(--color0) 45%,
      var(--color0) 55%,
      rgba(0, 0, 0, 0) 57%,
      rgba(0, 0, 0, 0) 100%
    ),
    linear-gradient(
      135deg,
      transparent 0%,
      transparent 43%,
      var(--color0) 45%,
      var(--color0) 55%,
      transparent 57%,
      transparent 100%
    );
}

/* filter */

.filter {
  width: 100%;
  max-width: 250px;
  border-radius: 5px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25);
  position: relative;
}

.filter button {
  width: 100%;
  height: 100%;
  cursor: pointer;
  border: none;
  border-radius: inherit;
  background: var(--color2);
  color: var(--color3);
}

.filter button:hover {
  color: var(--color4);
}

.filter button::after {
  font-family: "Material Symbols Outlined";
  content: "expand_more";
  width: 24px;
  height: 24px;
  font-size: 24px;
  line-height: 1;
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
}

.filter.open button::after {
  content: "expand_less";
}

.filter .options {
  position: absolute;
  top: calc(100% + 2px);
  z-index: 99;
  background: var(--color2);
  width: 100%;
  border-radius: inherit;
  box-shadow: inherit;
  overflow: hidden;
  opacity: 0;
  transform: scaleY(0);
  transform-origin: top;
  transition: 0.2s;
}

.filter.open .options {
  opacity: 1;
  transform: scaleY(1);
}

.filter .options div {
  padding: 0.5rem 1rem;
  cursor: pointer;
}

.filter .options div:hover {
  background: var(--color1);
  color: var(--color4);
}

/* grid */

.grid-container {
  display: grid;
  grid-template-columns: auto;
  gap: 2rem;
}

.grid-container + .no-result {
  display: none;
  width: 100%;
  text-align: center;
}

.grid-container:empty + .no-result {
  display: block;
}

.grid-item {
  background: var(--color2);
  border-radius: 10px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.25);
  overflow: hidden;
}

.grid-item a {
  display: block;
}

.grid-item img {
  width: 100%;
  height: 200px;
  object-fit: cover;
  background: #ddd;
}

.grid-item div {
  padding: 1.5rem;
}

.grid-item h3 {
  font-size: 1rem;
  font-weight: 600;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  margin-bottom: 0.5rem;
}

.grid-item p {
  margin-bottom: 0.5rem;
}

.grid-item label {
  display: block;
  color: var(--color0);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

@media (min-width: 768px) {
  .grid-container {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* modal */
.modal {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1024;
  width: 100%;
  height: 100%;
  background: var(--color1);
  display: none;
  animation-name: fadeIn;
  animation-duration: 0.3s;
}

.modal.show {
  display: grid;
  place-items: center;
}

.modal img {
  max-width: 80%;
  max-height: 80vh;
  margin: auto;
  filter: drop-shadow(0 5px 10px rgba(0, 0, 0, 0.25));
}

.modal figcaption {
  margin-top: 1.5rem;
  text-align: center;
  font-size: 0.9rem;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--color0);
}

.modal button[data-dismiss] {
  position: absolute;
  top: 1.5rem;
  right: 1.5rem;
  width: 50px;
  height: 50px;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  background: transparent;
  color: var(--color3);
}

.modal button[data-dismiss]:hover {
  color: var(--color4);
}

.modal button[data-dismiss] span {
  font-size: 42px;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

 

JavaScript (script.js)

const API_URL = "https://restcountries.com/v3.1/all";
const FIELDS =
  "name,cca3,currencies,capital,region,population,continents,flags";

const filter = document.querySelector(".filter");
const gridContainer = document.querySelector(".grid-container");
const searchInput = document.getElementById("search");
const modal = document.querySelector(".modal");

//* variables
let countries = [];
let filteredCountries = [];
let search = null;
let continent = "All";

//* api functions
const fetchData = async (apiURL) => {
  try {
    const response = await fetch(apiURL);
    return await response.json();
  } catch (error) {
    console.error("Error fetching data", error);
    throw error;
  }
};

const getCountries = async () => {
  try {
    const apiURL = `${API_URL}?fields=${FIELDS}`;
    const data = await fetchData(apiURL);

    return data.sort((a, b) => {
      const nameA = a.name.common.toLowerCase();
      const nameB = b.name.common.toLowerCase();
      if (nameA < nameB) return -1;
      if (nameA > nameB) return 1;
      return 0;
    });
  } catch (error) {
    return [];
  }
};

//* render functions

const renderCountryList = () => {
  // use fragment to temporary build the list
  const fragment = document.createDocumentFragment();

  filteredCountries.forEach((country) => {
    // destructure
    const {
      name,
      flags,
      cca3,
      capital,
      population,
      currencies,
      region,
      continents,
    } = country;

    // change currencies from object to array
    const currencyList = Object.keys(currencies).map((code) => {
      const { name } = currencies[code];
      return `${name} (${code})`;
    });

    const gridItem = document.createElement("div");
    gridItem.classList.add("grid-item");
    gridItem.addEventListener("click", onGridItemClick);

    gridItem.innerHTML = `
    <a href="${flags.svg}">
      <img
        src="${flags.svg}"
        alt="${name.common} (${cca3})"
      />
    </a>
    <div>
      <h3>${name.common}</h3>

      <p>
        <label>Capital</label>
        ${capital.join(", ")}
      </p>
      <p>
        <label>Population</label>
        ${population.toLocaleString("en")}
      </p>
      <p>
        <label>Currencies</label>
        ${currencyList.join(", ")}
      </p>
      <p>
        <label>Region</label>
        ${region}
      </p>
      <p>
        <label>Continent</label>
        ${continents.join(", ")}
      </p>
    </div>`;

    fragment.appendChild(gridItem);
  });

  // add the virtual list to DOM
  gridContainer.replaceChildren(fragment);
};

const renderFilterList = () => {
  const optionList = filter.querySelector(".options");

  // get the continents from countries and sort alphabetically
  let continents = countries.flatMap((c) => c.continents).sort();

  // prepend 'All' and remove duplicates
  continents = ["All", ...new Set(continents)];

  continents.forEach((continent) => {
    const option = document.createElement("div");
    option.textContent = continent;
    option.addEventListener("click", onFilterOptionClick);

    optionList.appendChild(option);
  });
};

//* event handlers

const loadCountries = async () => {
  countries = await getCountries();
  filteredCountries = countries;
  renderCountryList();
  renderFilterList();
};

const handleEscapeKey = (e) => {
  if (e.key === "Escape") {
    modal.classList.remove("show");
  }
};

const applyFilters = () => {
  filteredCountries = countries;

  if (search !== null) {
    filteredCountries = filteredCountries.filter((country) => {
      // name, capital, code
      const name = country.name.common.toLowerCase();
      const capital = country.capital.join(", ").toLowerCase();
      const code = country.cca3.toLowerCase();

      return (
        name.includes(search) ||
        capital.includes(search) ||
        code.includes(search)
      );
    });
  }

  if (continent !== "All") {
    filteredCountries = filteredCountries.filter((country) => {
      return country.continents.includes(continent);
    });
  }
};

const onGridItemClick = (e) => {
  e.preventDefault();
  if (e.target.tagName === "IMG") {
    const { src, alt } = e.target;

    const img = modal.querySelector("img");
    const dismiss = modal.querySelector("[data-dismiss]");
    const caption = modal.querySelector("figcaption");

    img.src = src;
    img.alt = alt;
    caption.textContent = alt;
    dismiss.addEventListener("click", onDismissClick);

    modal.classList.add("show");
  }
};

const onDismissClick = () => {
  modal.classList.remove("show");
};

const onFilterOptionClick = (e) => {
  continent = e.target.textContent;

  // Change the selected filter
  const button = filter.querySelector("button");
  button.textContent = continent;

  // apply the search filter to filtered countries
  applyFilters();

  // re-render country list
  renderCountryList();
};

const onFilterButtonClick = () => {
  filter.classList.toggle("open");
};

const onSearchInput = () => {
  search = searchInput.value.trim().toLowerCase();

  // apply the search keyword to filtered countries
  applyFilters();

  // re-render country list
  renderCountryList();
};

//* event listeners

document.addEventListener("DOMContentLoaded", loadCountries);
document.addEventListener("keydown", handleEscapeKey);
filter.addEventListener("click", onFilterButtonClick);
searchInput.addEventListener("input", onSearchInput);

 

Explanation

  1. HTML Structure:
    • A simple HTML structure includes a container with an input field for the search term and an unordered list to display the filtered country names.
  2. CSS Styling:
    • The CSS styles the container and input elements for a clean, centered look.
    • The unordered list (ul) and list items (li) are styled to remove default styling and add custom borders and padding.
  3. JavaScript Functionality:
    • A list of country names is stored in an array.
    • An event listener is added to the search input to filter the country names based on the user input.
    • The input event triggers a function that filters the country array and updates the unordered list (ul) with the filtered results.

How to Run

  1. Create three files: index.html, styles.css, and script.js.
  2. Copy the respective code blocks into these files.
  3. Open the index.html file in a web browser to see the country search feature in action.

This simple implementation allows users to type in a search term and see a list of matching country names in real-time.

CEO Piyush Gupta


Reviews

There are no reviews yet. Be the first one to write one.


0.0
Rated 0 out of 5
0 out of 5 stars (based on 0 reviews)
Excellent0%
Very good0%
Average0%
Poor0%
Terrible0%