[구현테스트]_검색, 검색 히스토리, 검색 하이라이팅

hanseungjune·2023년 7월 12일
0

구현테스트

목록 보기
2/2
post-thumbnail

목표

  • 검색 기능 구현하기
  • 검색할 때, 해당 텍스트 하이라이팅하기
  • 검색하고 Enter 할 때, 해당 검색 기록 남기기(영구보존 : localStorage)

코드 및 로직

기본 DOM

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Searching & highlight</title>
    <style>
      .highlight {
        background-color: yellow;
      }
    </style>
  </head>
  <body>
    <h1>2. 검색이 필요해</h1>
    <hr />

    <main>
      <label>Search:</label>
      <input type="text" id="search-input" />
      <ul id="search-history"></ul>
      <section id="section"></section>
    </main>

    <a href="./index.html" class="back-link">&#8592; 뒤로가기</a>
  </body>
</html>

데이터 가져오기 및 기본 렌더링

let data = [];
const fetchData = async () => {
  const response = await fetch("index2.json");
  const arr = await response.json();
  data.push(...arr);

  // 1. 렌더링 먼저
  renderTable(data);
};

const renderTable = (data) => {
  const section = document.getElementById("section");
  const table = document.createElement("table");
  section.appendChild(table);

  const headers = ["ID", "Photographer", "Introduction"];
  const headerRow = document.createElement("tr");

  headers.forEach((headerText) => {
    const header = document.createElement("th");
    header.textContent = headerText;
    headerRow.appendChild(header);
  });
  table.appendChild(headerRow);

  const rows = data.map((element) => {
    const row = document.createElement("tr");
    const idCell = document.createElement("td");
    idCell.textContent = element.id;
    row.appendChild(idCell);

    const PhotographerCell = document.createElement("td");
    PhotographerCell.textContent = element.photographer;
    row.appendChild(PhotographerCell);

    const IntroductionCell = document.createElement("td");
    IntroductionCell.textContent = element.introduction;
    row.appendChild(IntroductionCell);

    return row;
  });

  rows.forEach((row) => {
    table.appendChild(row);
  });
};

// 2. 데이터 패칭
fetchData();

검색 기능

// 검색할 때 돌아가는 코드
let filteredData = [];
searchInput.addEventListener("input", handleSearch);

// 입력한 텍스트가 포함되면 필터링된 배열에 필터링하기
function handleSearch(event) {
  const searchText = event.target.value.toLowerCase();

  if (searchText === "") {
    renderTable(searchData);
    return;
  }

  filteredData = data.filter((item) => {
    return (
      String(item.id).toLowerCase().includes(searchText) ||
      item.photographer.toLowerCase().includes(searchText) ||
      item.introduction.toLowerCase().includes(searchText)
    );
  });
  renderTable(filteredData);
}

검색 하이라이트 기능

// 정규식 객체 regex가 true인 애들은 span으로 대체해버리기
function highlightSearchText(searchText, text) {
  const regex = new RegExp(`${searchText}`, "gi");
  return text.replace(
    regex,
    `<span class="highlight">${searchText}</span>`
  );
}

// 모든 문장을 순회하면서, 검색 텍스트가 포함되면 하이라이트
function applyHighlighting(searchText) {
  const rows = document.querySelectorAll("table tr");
  rows.forEach((row) => {
    const cells = row.querySelectorAll("td");
    cells.forEach((cell) => {
      const originalText = cell.textContent;
      const highlightedText = highlightSearchText(
        searchText,
        originalText
      );
      cell.innerHTML = highlightedText;
    });
  });
}

// 일단 공백없이 입력하게 하기
searchInput.addEventListener("input", (event) => {
  const searchText = event.target.value.trim();
  applyHighlighting(searchText);
});

검색 히스토리 기능

// 검색 히스토리 코드 작성
const searchHistoryLimit = 5;
const searchHistoryList = document.getElementById("search-history");
let searchHistory = [];

// 입력하고 Enter 치면
// 이미 입력했거나 아예 입력하지 않으면 기록안됨
// 최신순으로 입력되게 하기
// 5개 넘으면 오래된거 지워지게하기
// 검색기록은 로컬스토리지에 담아서 계속 남아있게하기
// 그리고 기록되고난후 입력창은 빈칸
// 그리고 원래대로 내용 렌더링하고
// 화면에 검색기록 렌더링하기
// 검색기록은 로컬스토리지에서 가져오기
searchInput.addEventListener("keypress", function (event) {
  if (event.key === "Enter") {
    const searchText = searchInput.value.trim();

    if (searchHistory.includes(searchText) || searchText === "") {
      return;
    }

    searchHistory.unshift(searchText);

    if (searchHistory.length > searchHistoryLimit) {
      searchHistory.pop();
    }

    localStorage.setItem(
      "searchHistory",
      JSON.stringify(searchHistory)
    );

    searchInput.value = "";

    renderTable(searchData);
    updateSearchHistory();
  }
});

function updateSearchHistory() {
  searchHistoryList.innerHTML = "";

  searchHistory.forEach((searchText) => {
    const listItem = document.createElement("li");
    listItem.textContent = searchText;
    searchHistoryList.appendChild(listItem);
  });
}

if (localStorage.getItem("searchHistory")) {
  searchHistory = JSON.parse(localStorage.getItem("searchHistory"));
}

updateSearchHistory();

로직 설명

  1. 검색 기능

    • 사용자는 <input> 요소를 통해 검색어를 입력할 수 있습니다.
      검색어 입력 시, input 이벤트가 발생하고 입력된 검색어를 받아와서 처리합니다.
    • 입력된 검색어를 소문자로 변환한 후, 데이터 배열 searchData를 필터링합니다.
    • data 배열에서 검색어가 ID, Photographer, 또는 Introduction에 포함된 항목들로 이루어진 filteredData 배열을 생성합니다.
    • renderTable 함수를 호출하여 filteredData를 기반으로 테이블을 업데이트하여 검색 결과를 화면에 표시합니다.
  2. 검색 하이라이트 기능

    • highlightSearchText 함수는 정규식을 사용하여 검색어와 일치하는 텍스트를 <span> 요소로 감싸 하이라이트 효과를 줍니다.
    • applyHighlighting 함수는 모든 테이블 셀을 순회하면서 검색어와 일치하는 텍스트를 찾아 highlightSearchText 함수를 사용하여 하이라이트 효과를 적용합니다.
    • 사용자가 검색어를 입력할 때마다 applyHighlighting 함수가 호출되어 화면에 검색어가 하이라이트된 텍스트가 표시됩니다.
  3. 검색 히스토리 기능

    • 사용자가 검색어를 입력하고 Enter 키를 누를 경우, 검색어를 검사하여 기존 검색 기록에 없고 공백이 아닐 경우에만 검색 히스토리에 추가됩니다.
    • 최신 검색어가 리스트의 맨 앞에 추가되며, 검색 히스토리 리스트는 최대 5개까지 유지됩니다.
    • 검색 히스토리는 로컬 스토리지에 저장되어 페이지를 다시 열었을 때도 유지됩니다.
    • 검색 히스토리 업데이트 시, updateSearchHistory 함수가 호출되어 로컬 스토리지에서 검색 히스토리를 가져와 화면에 표시합니다.

전체 코드(실습용)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Searching & highlight</title>
    <style>
      .highlight {
        background-color: yellow;
      }
    </style>
  </head>
  <body>
    <h1>2. 검색이 필요해</h1>
    <hr />

    <main>
      <label>Search:</label>
      <input type="text" id="search-input" />
      <ul id="search-history"></ul>
      <section id="section"></section>
    </main>

    <a href="./index.html" class="back-link">&#8592; 뒤로가기</a>
    <script>
      let data = [];
      const fetchData = async () => {
        const response = await fetch("index2.json");
        const arr = await response.json();
        data.push(...arr);

        // 1. 렌더링 먼저
        renderTable(data);
      };

      const renderTable = (data) => {
        const section = document.getElementById("section");
        const table = document.createElement("table");
        section.appendChild(table);

        const headers = ["ID", "Photographer", "Introduction"];
        const headerRow = document.createElement("tr");

        headers.forEach((headerText) => {
          const header = document.createElement("th");
          header.textContent = headerText;
          headerRow.appendChild(header);
        });
        table.appendChild(headerRow);

        const rows = data.map((element) => {
          const row = document.createElement("tr");
          const idCell = document.createElement("td");
          idCell.textContent = element.id;
          row.appendChild(idCell);

          const PhotographerCell = document.createElement("td");
          PhotographerCell.textContent = element.photographer;
          row.appendChild(PhotographerCell);

          const IntroductionCell = document.createElement("td");
          IntroductionCell.textContent = element.introduction;
          row.appendChild(IntroductionCell);

          return row;
        });

        rows.forEach((row) => {
          table.appendChild(row);
        });
      };

      // 2. 데이터 패칭
      fetchData();

      window.onload = function () {
        const currentUrl = window.location.href;

        if (currentUrl.includes("web")) {
          const link = document.querySelector(".back-link");
          link.href = "../";
        }
      };
    </script>
    <script>
      window.addEventListener("DOMContentLoaded", () => {
        const searchInput = document.getElementById("search-input");
        const section = document.getElementById("section");

        let searchData = [];
        const fetchData = async () => {
          const response = await fetch("index2.json");
          const arr = await response.json();
          searchData.push(...arr);

          // 2. 렌더링
          renderTable(searchData);
        };

        function renderTable(data) {
          section.innerHTML = "";

          const table = document.createElement("table");
          section.appendChild(table);

          const headers = ["ID", "Photographer", "Introduction"];
          const headerRow = document.createElement("tr");

          headers.forEach((headerText) => {
            const header = document.createElement("th");
            header.textContent = headerText;
            headerRow.appendChild(header);
          });
          table.appendChild(headerRow);

          data.forEach((element) => {
            const row = document.createElement("tr");
            const idCell = document.createElement("td");
            idCell.textContent = element.id;
            row.appendChild(idCell);

            const PhotographerCell = document.createElement("td");
            PhotographerCell.textContent = element.photographer;
            row.appendChild(PhotographerCell);

            const IntroductionCell = document.createElement("td");
            IntroductionCell.textContent = element.introduction;
            row.appendChild(IntroductionCell);

            table.appendChild(row);
          });
        }
        // 1. 데이터 패칭
        fetchData();

        // 검색할 때 돌아가는 코드
        let filteredData = [];
        searchInput.addEventListener("input", handleSearch);

        // 입력한 텍스트가 포함되면 필터링된 배열에 필터링하기
        function handleSearch(event) {
          const searchText = event.target.value.toLowerCase();

          if (searchText === "") {
            renderTable(searchData);
            return;
          }

          filteredData = data.filter((item) => {
            return (
              String(item.id).toLowerCase().includes(searchText) ||
              item.photographer.toLowerCase().includes(searchText) ||
              item.introduction.toLowerCase().includes(searchText)
            );
          });
          renderTable(filteredData);
        }

        // 정규식 객체 regex가 true인 애들은 span으로 대체해버리기
        function highlightSearchText(searchText, text) {
          const regex = new RegExp(`${searchText}`, "gi");
          return text.replace(
            regex,
            `<span class="highlight">${searchText}</span>`
          );
        }

        // 모든 문장을 순회하면서, 검색 텍스트가 포함되면 하이라이트
        function applyHighlighting(searchText) {
          const rows = document.querySelectorAll("table tr");
          rows.forEach((row) => {
            const cells = row.querySelectorAll("td");
            cells.forEach((cell) => {
              const originalText = cell.textContent;
              const highlightedText = highlightSearchText(
                searchText,
                originalText
              );
              cell.innerHTML = highlightedText;
            });
          });
        }

        // 일단 공백없이 입력하게 하기
        searchInput.addEventListener("input", (event) => {
          const searchText = event.target.value.trim();
          applyHighlighting(searchText);
        });

        // 검색 히스토리 코드 작성
        const searchHistoryLimit = 5;
        const searchHistoryList = document.getElementById("search-history");
        let searchHistory = [];

        // 입력하고 Enter 치면
        // 이미 입력했거나 아예 입력하지 않으면 기록안됨
        // 최신순으로 입력되게 하기
        // 5개 넘으면 오래된거 지워지게하기
        // 검색기록은 로컬스토리지에 담아서 계속 남아있게하기
        // 그리고 기록되고난후 입력창은 빈칸
        // 그리고 원래대로 내용 렌더링하고
        // 화면에 검색기록 렌더링하기
        // 검색기록은 로컬스토리지에서 가져오기
        searchInput.addEventListener("keypress", function (event) {
          if (event.key === "Enter") {
            const searchText = searchInput.value.trim();

            if (searchHistory.includes(searchText) || searchText === "") {
              return;
            }

            searchHistory.unshift(searchText);

            if (searchHistory.length > searchHistoryLimit) {
              searchHistory.pop();
            }

            localStorage.setItem(
              "searchHistory",
              JSON.stringify(searchHistory)
            );

            searchInput.value = "";

            renderTable(searchData);
            updateSearchHistory();
          }
        });

        function updateSearchHistory() {
          searchHistoryList.innerHTML = "";

          searchHistory.forEach((searchText) => {
            const listItem = document.createElement("li");
            listItem.textContent = searchText;
            searchHistoryList.appendChild(listItem);
          });
        }

        if (localStorage.getItem("searchHistory")) {
          searchHistory = JSON.parse(localStorage.getItem("searchHistory"));
        }

        updateSearchHistory();
      });
    </script>
  </body>
</html>

구현 화면

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글