본문 바로가기
프로그래머스

[프로그래머스]프로그래밍 언어 검색

by 남민섭 2023. 3. 3.
728x90
반응형

문제: 프로그래밍 언어 검색

 

 

index.html 

이런 형태의 화면을 구성해야한다 index.html을 참고하여 javascript를 컴포넌트로 분리하고 각 태그들을 javascript를 사용하여 구현해야함 

<html>
  <head>
    <title>2022 FE 데브매칭</title>
    <link rel="stylesheet" href="./style.css">
  </head>
  <body>
    <main class="App">
      <div class="SelectedLanguage">
        <ul>
          <li>JavaScript</li>
          <li>Python</li>
          <li>Elixir</li>
          <li>Java</li>
          <li>PHP</li>
        </ul>
      </div> 
      <form class="SearchInput">
        <input class="SearchInput__input" type="text" placeholder="프로그램 언어를 입력하세요." value="Script">
      </form>
      <div class="Suggestion">
        <ul>
          <li class="Suggestion__item--selected">
          Action<span class="Suggestion__item--matched">Script</span></li>
          <li>Java<span class="Suggestion__item--matched">Script</span></li>
          <li>Type<span class="Suggestion__item--matched">Script</span></li>
          <li>Pure<span class="Suggestion__item--matched">Script</span></li>
        </ul>
      </div> 
    </main>
    <script src="components/index.js" type="module"></script>
  </body>
</html>

 

 

 

1. 초기 파일 만들기

Id값이 App인 div태그 하나를 만들어서 그 안에 각 컴포넌트들을 조립해보자

그 전에 javascript로 잘 적용 되는지 초기 파일을 만들어보자

index.html

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

기존에 있던 index.html에 main태그 빼고 다 지움 그리고 index.js 불러오기함

 

components폴더 생성

index.js

import App from "./App.js";

new App({ target: document.querySelector('.App') })

class 명이 App인 main태그를 선택하고 App.js 파일을 만들어서 props로 전달해줄거다

 

components폴더

App.js

export default function App({target}) { //구조분해할당
    this.render = () => {
        target.innerHTML = `
        <h1>안녕하세요</h1>
        `;
    }
    this.render(); //render 호출
}

 

 


2. SearchInput.js 컴포넌트 구현하기

SearchInput.js 생성

export default function SearchInput({target, initialState}){
    this.state = initialState;
    //폼태그 생성
    this.element = document.createElement('form');
    //class명 생성
    this.element.className = "searchForm";
    //app 요소에 form태크 추가하기
    target.appendChild(this.element);
    this.render = () => {
        this.element.innerHTML = `
            <input type="text" value="${this.state}" placeholder="검색할 언어를 입력하세요"/>
        `
    }
    this.render(); // render()호출
}

App.js 

app에 SearchInput 조립하기

import SearchInput from "./SearchInput.js"

export default function App ({target}){
   /* this.render = () => {
        target.innerHTML =`
        <h1>안녕하세요</h1>
        `
        console.log("aaaaa")
    }
    this.render()*/ //테스트로 만든건 지워주기!!!!
    // SearchInput 조립
    const searchInput = new SearchInput({ //target과 initialState를 props로 전달
        target,
        initialState: ''
    })
}

 

api폴더 생성

api.js생성

서버에 데이터 요청

export const END_FATCH_POINT = 'http://localhost:3006'  // 내가 만든 서버 주소
const request = async (url) => {
    //url경로로 fetch전송
    const res = await fetch(url);

    if(res) { // 데이터를 받아올경우 json형태로 변환해서 리턴
        const json = await res.json()
        return json
    }
    throw new Error('문제가 생겼습니다')
}

export const fetchList = async (keyword) => 
request(`${END_FATCH_POINT}/languages?keyword=${keyword}`)

 

App.js

onChange를 props로 전달

import { fetchList } from "../api/api.js"
import SearchInput from "./SearchInput.js"

export default function App ({target}){
    // SearchInput 조립
    const searchInput = new SearchInput({ //target과 initialState를 props로 전달
        target,
        initialState: '',
        //onChange함수 생성
        onChange: async (keyword) => { //SearchInput 에서 input에 value 값을 받으면 여기에서
            // 서버로 키워드에 맞게 데이터 요청함 그걸 콘솔에 출력
            const language = await fetchList(keyword)
            console.log(language)
        }
    })
}

 

SearchInput.js 

keyup이벤트 사용(키를 눌렀을때 props로 받은 onChange 실행)

export default function SearchInput({target, initialState, onChange}){
    this.state = initialState;
    //폼태그 생성
    this.element = document.createElement('form');
    //class명 생성
    this.element.className = "searchForm";
    //app 요소에 form태크 추가하기
    target.appendChild(this.element);
    this.element.addEventListener('keyup', (e)=> { //키를 눌렀을때 작동하는 이벤트
        onChange(e.target.value)
    })
    this.render = () => {
        this.element.innerHTML = `
            <input type="text" value="${this.state}" placeholder="검색할 언어를 입력하세요"/>
        `
    }
    this.render(); // render()호출
}

콘솔에 잘 출력된다

 

App.js

출력받은 데이터를 상태업데이트 시켜주자

일단 상태관리할 코드 작성 후 상태 업데이트 시켜주자!!

 

import { fetchList } from "../api/api.js"
import SearchInput from "./SearchInput.js"

export default function App ({target}){
    //app에서 관리할 상태값
    this.state = {
        fetchedlist: []
    }
    //상태값 업데이트
    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }
    }
    // SearchInput 조립
    const searchInput = new SearchInput({ //target과 initialState를 props로 전달
        target,
        initialState: '',
        //onChange함수 생성
        onChange: async (keyword) => {  
            if(keyword.length > 0){ 
                //SearchInput에서 받아온 input value값 길이가 0 이상이면 아래 실행
                const language = await fetchList(keyword)
                this.setState({ //받아온 데이터를 상태 업데이트
                    fetchedlist: language
                })
                console.log(this.state.fetchedlist)
            } else { // input에 value값 길이가 0 이하면 빈배열 출력력
                this.setState({
                    fetchedlist: []
                })
            }
            
        }
    })
}

 


3. Suggestion.js 컴포넌트 구현하기

input에 입력했을 때 그 키워드와 관련된 언어리스트 input 아래에 출력

 

components폴더

Suggestion.js

export default function Suggestion({target, initialState}) {
    this.element = document.createElement('div') //div 태그 생성
    this.element.className = 'suggestion' 
    target.appendChild(this.element) //app 에 추가
    this.state = {  //Suggestion 상태관리
        fetchlist: initialState,
        selectedIndex: 0
    }
    this.setState = (nextState) => {  //상태 업데이트
        this.state = {
            ...this.state,
            ...nextState
        }
        this.render(); //업데이트 후 render 호출
    }
    this.render = () =>{
        const {fetchlist = [], selectedIndex} = this.state; //상태 구조분해
        if(fetchlist.length > 0) {  //fetchlist 길이가 0이상이면 생성
            this.element.style.display = 'block';
            this.element.innerHTML =`
            <ul>
                ${fetchlist.map((list,index)=> `<li data-index="${index}">${list}</li>`).join("")}
            </ul>
            `;
        }else { //fetchlist 길이가 0이하
            console.log(111)
            this.element.style.display = 'none'
            this.element.innerHTML = ``;
        }
    }
    this.render() //render 호출
}

 

App.js

Suggestion 조립 / suggestion 상태 업데이트 추가

import { fetchList } from "../api/api.js"
import SearchInput from "./SearchInput.js"
import Suggestion from "./Suggestion.js"

export default function App ({target}){
    //app에서 관리할 상태값
    this.state = {
        fetchedlist: []
    }
    //상태값 업데이트
    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }
        //상태값이 업데이트 되면서 Suggestion도 업데이트 
        suggestion.setState({fetchlist: this.state.fetchedlist})
    }
    // SearchInput 조립
    const searchInput = new SearchInput({ //target과 initialState를 props로 전달
        target,
        initialState: '',
        //onChange함수 생성
        onChange: async (keyword) => {  
            if(keyword.length > 0){ 
                //SearchInput에서 받아온 input value값 길이가 0 이상이면 아래 실행
                const language = await fetchList(keyword)
                this.setState({ //받아온 데이터를 상태 업데이트
                    fetchedlist: language
                })
                console.log(this.state.fetchedlist)
            } else { // input에 value값 길이가 0 이하면 빈배열 출력력
                this.setState({
                    fetchedlist: []
                })
            }
            
        }
    })
    //Suggestion 조립
    const suggestion = new Suggestion({ //target과 initialState를 props로 전달
        target,
        initialState: []
    })
}

 

Suggestion.js

li에 class명 추가, 방향키로 키워드 관련 리스트 선택

export default function Suggestion({target, initialState}) {
    this.element = document.createElement('div') //div 태그 생성
    this.element.className = 'suggestion' 
    target.appendChild(this.element) //app 에 추가
    this.state = {  //Suggestion 상태관리
        fetchlist: initialState,
        selectedIndex: 0
    }
    this.setState = (nextState) => {  //상태 업데이트
        this.state = {
            ...this.state,
            ...nextState
        }
        this.render(); //업데이트 후 render 호출
    }
    this.render = () =>{
        const {fetchlist = [], selectedIndex} = this.state; //상태 구조분해
        if(fetchlist.length > 0) {  //fetchlist 길이가 0이상이면 생성
            this.element.style.display = 'block';
            this.element.innerHTML =`
            <ul>
                ${fetchlist.map((list,index)=>`
                <li data-index="${index}" class="${index === selectedIndex ?
                'Suggestion__item--selected' : ''}">${list}</li>`).join("")}
            </ul>
            `;
        }else { //fetchlist 길이가 0이하
            console.log(111)
            this.element.style.display = 'none'
            this.element.innerHTML = ``;
        }
    }
    this.render() //render 호출
    //방향키 up, down이벤트 연결하기
    //방향키 위를 누르면 index는 현재에서 -1
    //방향키 아래르 누르면 index는 현재에서 1
    window.addEventListener('keyup', (e)=>{
        const {fetchlist = [], selectedIndex} = this.state;
        const inclusionKeys = ['ArrowUp', 'ArrowDown'];
        let nextIndex = selectedIndex;
        const lastIndex = fetchlist.length -1;
        if(inclusionKeys.includes(e.key)) {
            if(e.key === 'ArrowUp') { //위 방향키일때
                nextIndex = nextIndex === 0 ? lastIndex : nextIndex -1;
            }else {//아래 방향키일 때  nextIndex 값이 lastIndex 하고 같은면 0초기화
                nextIndex = nextIndex === lastIndex ? 0 : nextIndex + 1;
            }
            this.setState({ //업데이트
                selectedIndex: nextIndex
            })
            //엔터키를 눌렀을때 해당 키워드가 알람으로 뜸 빈배열일경우 undefined
        }else if (e.key === 'Enter') {
            alert(fetchlist[this.state.selectedIndex])
        }
    })
}

 

SearchInput.js 수정

정해놓은 배열안에 있는 키를 제외한 키를 눌렀을 때 이벤트 추가

submit이벤트 제거 추가

export default function SearchInput({target, initialState, onChange}){
    this.state = initialState;
    //폼태그 생성
    this.element = document.createElement('form');
    //class명 생성
    this.element.className = "searchForm";
    //app 요소에 form태크 추가하기
    target.appendChild(this.element);

    this.element.addEventListener('keyup', (e)=> { //키를 눌렀을때 작동하는 이벤트
        //해당 배열 제외한 키를 눌렀을때 이벤트 발생
        const excepList = ['Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']
        if(!excepList.includes(e.key)){
            onChange(e.target.value);
        }
    })
    //submit이벤트 제거
    this.element.addEventListener('submit', (e)=>{
        e.preventDefault();
    })
    
    this.render = () => {
        this.element.innerHTML = `
            <input type="text" value="${this.state}" placeholder="검색할 언어를 입력하세요"/>
        `
    }
    this.render(); // render()호출
}

 


4. SelectLists.js 컴포넌트 구현하기

키워드 선택 후 엔터 눌렀을때 input위에 키워드 추가하기

 

components폴더

SelectLists.js

export default function SelectLists({target, initialState}) {
    this.element = document.createElement('div'); //div 태그 생성
    this.element.className ="selectlists" 
    target.appendChild(this.element); // app에 div태그 추가
    this.state = { //SelectLists 상태관리
        selects : initialState
    }
    this.setState = (newState) => { //업데이트
        console.log(1123123)
        this.state = {
            selects: newState
        };
        console.log(this.state)
        this.render(); //업데이트 후 render 호출
    }
    this.render = () => {
        const {selects = [] } = this.state; //상태 구조분해
        console.log(selects)
        this.element.innerHTML = `
        <ul>
            ${selects.map(list=>`<li>${list}</li>`).join('')}
        </ul>
        `;// 
    }
    this.render() // 호출
}

 

App.js

selectLists 조립, 상태관리 selectedlist 추가 ,Suggestion 프로퍼티 추가(onSelect) 후  업데이트

import { fetchList } from "../api/api.js"
import SearchInput from "./SearchInput.js"
import SelectLists from "./SelectLists.js"
import Suggestion from "./Suggestion.js"

export default function App ({target}){
    //app에서 관리할 상태값
    this.state = {
        selectedlist: [],
        fetchedlist: []
    }
    //상태값 업데이트
    this.setState = (newState) => {
        this.state = {
            ...this.state,
            ...newState
        }
        //상태값이 업데이트 되면서 Suggestion도 업데이트 
        suggestion.setState({fetchlist: this.state.fetchedlist})
        selectLists.setState(this.state.selectedlist)
    }
    //SelectLists 조립 참고 index.html파일 보면 위치 확인 가능 위치에 맞게 조립할것
    const selectLists = new SelectLists({
        target,
        initialState: []
    })

    // SearchInput 조립
    const searchInput = new SearchInput({ //target과 initialState를 props로 전달
        target,
        initialState: '',
        //onChange함수 생성
        onChange: async (keyword) => {  
            if(keyword.length > 0){ 
                //SearchInput에서 받아온 input value값 길이가 0 이상이면 아래 실행
                const language = await fetchList(keyword)
                this.setState({ //받아온 데이터를 상태 업데이트
                    fetchedlist: language
                })
                console.log(this.state.fetchedlist)
            } else { // input에 value값 길이가 0 이하면 빈배열 출력력
                this.setState({
                    fetchedlist: []
                })
            }
            
        }
    })
    //Suggestion 조립
    const suggestion = new Suggestion({ //target과 initialState를 props로 전달
        target,
        initialState: [],
        onSelect: (list) => {
            console.log(list)
            const {selectedlist} = this.state;
            console.log(selectedlist)
            const index = selectedlist.findIndex(selist=>selist === list) 
            //참이면 해당 인덱스 반환 거짓이면 -1반환
            //중복된 값 제거(기존배열에 있던거 제거함)
            console.log(index)
            if(index > -1) {
                console.log("중복제거")
                selectedlist.splice(index,1) 
            }
            selectedlist.push(list) // 배열에 추가해주기
            console.log(selectedlist)
            //배열 갯수 5개로 제한해주기
            if(selectedlist.length > 5) {
                const startPosition = selectedlist.length -5 
                console.log(startPosition)
                // selectedlist가 6일경우 startPosition = 1
                const nselectedlist = selectedlist.slice(startPosition, 5 + startPosition)
                // selectedlist.slice(1 , 6) 1~5째까지 자르기 0번째꺼는 제거됨
                this.setState({selectedlist: nselectedlist}) // 업데이트
            }else{ //5이하일경우  기존 배열 그대로
                this.setState({selectedlist: selectedlist}) 
            }
        }
    })
}

Suggestion.js 

onSelect props 추가, 맨 밑에 엔터를 눌렀을때 onSelect  매개변수 전송

export default function Suggestion({target, initialState, onSelect}) {
    this.element = document.createElement('div') //div 태그 생성
    this.element.className = 'suggestion' 
    target.appendChild(this.element) //app 에 추가
    this.state = {  //Suggestion 상태관리
        fetchlist: initialState,
        selectedIndex: 0
    }
    this.setState = (nextState) => {  //상태 업데이트
        this.state = {
            ...this.state,
            ...nextState
        }
        this.render(); //업데이트 후 render 호출
    }
    this.render = () =>{
        const {fetchlist = [], selectedIndex} = this.state; //상태 구조분해
        if(fetchlist.length > 0) {  //fetchlist 길이가 0이상이면 생성
            this.element.style.display = 'block';
            this.element.innerHTML =`
            <ul>
                ${fetchlist.map((list,index)=>`
                <li data-index="${index}" class="${index === selectedIndex ?
                'Suggestion__item--selected' : ''}">${list}</li>`).join("")}
            </ul>
            `;
        }else { //fetchlist 길이가 0이하
            console.log(111)
            this.element.style.display = 'none'
            this.element.innerHTML = ``;
        }
    }
    this.render() //render 호출
    //방향키 up, down이벤트 연결하기
    //방향키 위를 누르면 index는 현재에서 -1
    //방향키 아래르 누르면 index는 현재에서 1
    window.addEventListener('keyup', (e)=>{
        const {fetchlist = [], selectedIndex} = this.state;
        const inclusionKeys = ['ArrowUp', 'ArrowDown'];
        let nextIndex = selectedIndex;
        const lastIndex = fetchlist.length -1;
        if(inclusionKeys.includes(e.key)) {
            if(e.key === 'ArrowUp') { //위 방향키일때
                nextIndex = nextIndex === 0 ? lastIndex : nextIndex -1;
            }else {//아래 방향키일 때  nextIndex 값이 lastIndex 하고 같은면 0초기화
                nextIndex = nextIndex === lastIndex ? 0 : nextIndex + 1;
            }
            this.setState({ //업데이트
                selectedIndex: nextIndex
            })
            //엔터키를 눌렀을때 해당 키워드가 input위에 등록됨
        }else if (e.key === 'Enter') {
            alert(fetchlist[this.state.selectedIndex])
            onSelect(fetchlist[this.state.selectedIndex])
        }
    })
}

 

style.css

* { margin: 0; padding:0; box-sizing: border-box;}
li { 
    list-style: none;
}
body {
    font-size: 14px;
    line-height: 1.5;
}
#App {
   width: 100%;
   max-width: 400px;
   margin: 80px auto;
   padding: 50px; 
}
#App > div {
    margin: 10px 0;
}
.Suggestion__item--selected {
    background-color: aquamarine;
}
.searchForm input {
    width: 100%;
    line-height: 30px;
    border-radius: 15px;
    outline: none;
    border: 1px solid #ccc;
    padding: 0 20px;
}
.suggestion {
    border-radius: 15px;
    border: 1px solid #ccc;
    padding: 10px;
}
.suggestion li {
    border-radius: 8px;
    padding: 6px;
}
.selectlists ul {
    display: flex;
    font-size: 12px;
    text-align: center;
    line-height: 26px;
    justify-content: center;
}
.selectlists ul li {
    padding: 0 6px;
    border-radius:6px;
    margin: 0 4px;
    border: 1px solid #ccc;
}

끝!!!!!!!!!!!!!!!!!!!!!!!!!

728x90
반응형

'프로그래머스' 카테고리의 다른 글

[프로그래머스]커스텀 라우터  (0) 2023.03.04

댓글