프로그래머스 프로그래밍 언어 검색 (완)

김영우·2022년 10월 1일
3
post-thumbnail

(완) 전체 코드

- index.html

<html>
  <head>
    <title>2022 FE 데브매칭</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <main class="App">
      <ul class="SelectedLanguage"></ul>
    </main>
    <script src="/index.js" type="module"></script>
  </body>
</html>

- index.js

import App from "./App.js";

new App({ target: document.querySelector(".App") });

// 전체적인 구조

// App 다른 기능들을 통제하는 중앙 제어 컴포넌트 느낌
// 화면에 보여지거나 처리되는 대부분의 정보를 state로 가지고 있고,
// 다른 컴포넌트들에서 사용되어지도록 전달해주기

// SearchInput
// 실질적으로 입력을 받는 부분
// App의 setState를 사용하여 입력된 정보를 App 컴포넌트에 전달해주기

// SelectedLanguage
// 엔터키를 통해 선택된 언어 배열을 전달받아 보여주는 컴포넌트
// 딱히 무언가 변경하지 않고 보여주기만 하기

// Suggestion
// api를 사용하여 넘겨받은 정보를 화면에 보여주는 컴포넌트
// 방향키를 사용하여 선택하거나 마우스 클릭을 통해 선택되어진 언어를 App에 전달해주기

// api
// 입력된 값을 통해 변경되는 API주소에 요청을 넣어 반환값을 돌려주는 컴포넌트
// 얘를 가지고 Input에서 무언가 변화가 있을 때 API요청을 넣고, 반환되는 값을 App에 전달해주기

- App.js

import SearchInput from "./SearchInput.js";
import SelectedLanguages from "./SelectedLanguages.js";
import Suggestion from "./Suggestion.js";

export default function App({ target }) {
  this.state = {
    searchResult: [],
    selectedLanguages: [],
    currentInput: "",
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    suggestion.setState({
      searchResult: this.state.searchResult,
      selectedIndex: 0,
      currentInput: this.state.currentInput,
    });
  };

  const searchInput = new SearchInput({
    target: target,
    onChange: (fetchedLanguages) => {
      this.setState({
        searchResult: fetchedLanguages,
      });
    },
    setCurrentInput: (input) => {
      this.setState({
        ...this.state,
        currentInput: input,
      });
    },
  });

  const suggestion = new Suggestion({
    target: target,
    selectLanguage: (language) => {
      if (!this.state.selectedLanguages.includes(language)) {
        let selectedArray = [...this.state.selectedLanguages, language];
        if (selectedArray.length > 5) {
          selectedArray = selectedArray.splice(1, 5);
        }
        this.state = {
          ...this.state,
          selectedLanguages: selectedArray,
        };
        selectedLanguages.setState({
          selectedLanguages: this.state.selectedLanguages,
        });
        console.log(this.state.selectedLanguages);
      }
    },
  });

  const selectedLanguages = new SelectedLanguages({
    target: document.querySelector(".SelectedLanguage"),
    deleteLanguage: (language) => {
      this.state = {
        ...this.state,
        selectedLanguages: this.state.selectedLanguages.filter(
          (l) => l !== language
        ),
      };
      selectedLanguages.setState({
        selectedLanguages: this.state.selectedLanguages,
      });
    },
  });
}

- api.js

const API_END_POINT = `https://wr4a6p937i.execute-api.ap-northeast-2.amazonaws.com/dev`;

const request = async (url) => {
  const response = await fetch(url);
  if (response.ok) {
    return response.json();
  }
  throw new Error("요청에 실패했습니다.");
};

export const Fetch = async (input) =>
  request(`${API_END_POINT}/languages?keyword=${input}`).catch((Error) =>
    console.log(Error.message)
  );

- SearchInput.js

import { Fetch } from "./api.js";

export default function SearchInput({ target, onChange, setCurrentInput }) {
  this.element = document.createElement("form");
  this.element.className = "SearchInput";
  this.element.innerHTML = `
    <input class="SearchInput__input" placeholder="프로그램 언어를 입력하세요."/>
  `;
  this.element.addEventListener("submit", (e) => {
    e.preventDefault();
  });

  target.appendChild(this.element);

  this.element.addEventListener("keyup", async (e) => {
    const inActivateKey = ["ArrowUp", "ArrowDown", "Enter"];
    const keyword = e.target.value;

    if (!inActivateKey.includes(e.key)) {
      setCurrentInput(keyword);
      if (keyword !== "")
        await Fetch(keyword).then((data) => {
          onChange(data);
        });
      else onChange([]);
    }
  });
}

- Suggestion.js

export default function Suggestion({ target, selectLanguage }) {
  this.element = document.createElement("ul");
  this.element.className = "Suggestion";
  this.element.style.display = "none";

  target.appendChild(this.element);

  this.state = {
    searchResult: [],
    selectedIndex: 0,
    currentInput: "",
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    this.render();
  };

  this.render = () => {
    const searchResult = this.state.searchResult;
    const selectedIndex = this.state.selectedIndex;
    if (searchResult.length > 0) {
      this.element.innerHTML = `
            ${searchResult
              .map((item, index) => {
                const regexp = new RegExp(this.state.currentInput, "ig");
                const language = item.replace(
                  regexp,
                  `<span class="Suggestion__item--matched">$&</span>`
                );
                return `<li class="${
                  selectedIndex === index ? "Suggestion__item--selected" : ""
                }">${language}</li>`;
              })
              .join("")}
        `;
      this.element.style.display = "block";
    } else {
      this.element.style.display = "none";
    }
  };

  window.addEventListener("keyup", (e) => {
    const activateKey = ["ArrowUp", "ArrowDown", "Enter"];
    const lastIndex = this.state.searchResult.length - 1;
    if (activateKey.includes(e.key)) {
      switch (e.key) {
        case "ArrowUp":
          this.setState({
            selectedIndex:
              this.state.selectedIndex === 0
                ? lastIndex
                : this.state.selectedIndex - 1,
          });
          break;
        case "ArrowDown":
          this.setState({
            selectedIndex:
              this.state.selectedIndex === lastIndex
                ? 0
                : this.state.selectedIndex + 1,
          });
          break;
        case "Enter":
          selectLanguage(this.state.searchResult[this.state.selectedIndex]);
          break;
      }
    }
  });

  this.element.addEventListener("click", (e) => {
    selectLanguage(
      e.target.innerHTML
        .replace(/\<span class=\"Suggestion\_\_item\-\-matched\"\>/g, "")
        .replace(/\<\/span\>/g, "")
    );
  });

  this.render();
}

- SelectedLanguages.js

export default function SelectedLanguages({ target, deleteLanguage }) {
  this.state = {
    selectedLanguages: [],
  };

  this.setState = (nextState) => {
    this.state = {
      ...this.state,
      ...nextState,
    };
    this.render();
  };

  this.render = () => {
    target.innerHTML = `
        ${this.state.selectedLanguages
          .map(
            (item) =>
              `<li style="margin-right: 8px; cursor: pointer;">${item}</li>`
          )
          .join("")}
    `;
  };

  target.addEventListener("click", (e) => {
    console.log(e.target.innerHTML);
    deleteLanguage(e.target.innerHTML);
  });

  this.render();
}

css파일은 따로 올리지 않았습니다. 프로그래머스 문제를 직접 풀어보며 확인해보시길 바랍니다!
추가적으로 선택된 언어의 삭제 기능도 한번 구현해보았으니 혹시나 보시는 분이 있다면 분석해보시는 것도 좋을 것 같습니다!!

profile
불편한 일들을 개발로 풀어내고 싶은 프론트엔드 개발자입니다!

0개의 댓글