Javascript30 - Day6 Ajax Type Ahead with fetch

주제설명

fetch()를 활용하여 도시 정보 가져온 후 검색어에 해당하는 도시 목록 출력

결과물

See the Pen by hyunwk (@hyunwk) on CodePen.

JS

		const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
    const cities = [];

    fetch(endpoint)
      .then(blob => blob.json())
      .then(data => cities.push(...data)); //data안의 값들을 spread 한다.
    // .then(data => cities.push(data)); data란 어레이를 원소로 가진다.
    //.then(data => cities = data); cities를 let으로 바꾸면 가능

    function findMatches(wordToMatch, cities) {
      return cities.filter(place => {
        const regex = new RegExp(wordToMatch, 'gi') //g = global, i = insensitive 대소문자 가리지 않는다.
        return place.city.match(regex) || place.state.match(regex);
        //regex 에 변수 할당
      });
    }

    function numberWithCommas(n) {
      return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    function displayMatches() {
      const matchArray = findMatches(this.value, cities);
      const html = matchArray.map(place => {
        const regex = new RegExp(this.value, 'gi');
        const cityName = place.city.replace(regex, `<span class="hl">${this.value}</span>`);
        const stateName = place.state.replace(regex, `<span class="hl">${this.value}</span>`);
        return `
          <li>
	          <span class="name">${cityName}, ${stateName}</span>
	          <span class="population">${numberWithCommas(place.population)}</span>
	        </li>
        `;
      }).join('');
      suggestions.innerHTML = html;
    }

    const serachInput = document.querySelector('.search');
    const suggestions = document.querySelector('.suggestions');

    serachInput.addEventListener('change', displayMatches);
    serachInput.addEventListener('keyup', displayMatches);

fetch

		const endpoint = 'https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json';
    const cities = [];

    fetch(endpoint)
      .then(blob => blob.json())
      .then(data => cities.push(...data));
  1. fetch(endpoint)로 url에 요청 후 promise 를 반환한다.
  2. then으로 실행 시 blob(타입이 있는 바이너리 데이터)를 반환하므로 blobjson으로 변경한다.
  3. json 타입의 값들을 spread 연산(…)을 통해 cities에 push 한다.
    • .then(data ⇒ cities.push(data)) 를 하지 않는 이유는

    data란 array를 cities가 원소를 가지기 때문에 cities = [Array[1000]] 이런식으로 묶여 있기 때문이다.

  • cities는 const 이기 때문에 , .then(data ⇒ cities = data) 불가

    cities를 let으로 선언하면 가능하다.

addEventListner

    const serachInput = document.querySelector('.search');
    const suggestions = document.querySelector('.suggestions');

    serachInput.addEventListener('change', displayMatches);
    serachInput.addEventListener('keyup', displayMatches);

searchInput, 즉 입력창에 값이 change 되거나 keyup(키를 놓을 때)인 경우 displayMatches함수를 실행하게 했다.

regex 선언

1. let re = /ab+c/;
2. let re = new RegExp("ab+c");
2. let value = "abc";
	 let re = new RegExp(value, 'gi');

정규식을 만드는 방법에는 두가지가 있다.

  1. 정규식이 상수라면, 정규식 리터러를 사용하는것이 효율적이다.
  2. 정규식이 변수라면, RegExp객체의 생성자 함수를 호출해서 변수를 사용한다.

정규식 플래그

  • g = global : 전역으로 검색한다.
  • i = insensitive 대소문자 구분없이 검색한다

displayMatches

    function displayMatches() {
      const matchArray = findMatches(this.value, cities);
      const html = matchArray.map(place => {
        const regex = new RegExp(this.value, 'gi');
        const cityName = place.city.replace(regex, `<span class="hl">${this.value}</span>`);
        const stateName = place.state.replace(regex, `<span class="hl">${this.value}</span>`);
        return `
          <li>
	          <span class="name">${cityName}, ${stateName}</span>
	          <span class="population">${numberWithCommas(place.population)}</span>
	        </li>
        `;
      }).join('');
      suggestions.innerHTML = html;
    }
  1. findMatches함수로 검색한 값과 일치하는 array를 return한다.
  2. 일치하는 array에서 검색어와 일치하는 부분만 html태그로 감싸 class="hl"로 만들어서 배경 색을 바꾸었다.
  3. html li 태그를 사용하여 결과값을 목록으로 반환하여 html 상수에 할당하였다.
  4. suggestions 클래스에 html 상수를 설정하였다.

findMatches

    function findMatches(wordToMatch, cities) {
      return cities.filter(place => {
        const regex = new RegExp(wordToMatch, 'gi') 
        return place.city.match(regex) || place.state.match(regex);
      });
    }
  1. cities(도시 목록들)에서 filter에 해당하는 값들을 새로운 array로 만들어 return 하였다.
  2. place.city, place.state에서 wordToMatch가 포함되는지 검색하였다.
  3. 검색은 regex로 값이 포함되는지 확인하는 match 함수를 사용하였다.

HTML

<form class="search-form">
    <input type="text" class="search" placeholder="City or State">
    <ul class="suggestions">
      <li>Filter for a city</li>
      <li>or a state</li>
    </ul>
  </form>

CSS

html {
  box-sizing: border-box;
  background: #ffc600;
  font-family: 'helvetica neue';
  font-size: 20px;
  font-weight: 200;
}

*, *:before, *:after {
  box-sizing: inherit;
}

input {
  width: 100%;
  padding: 20px;
}

.search-form {
  max-width: 400px;
  margin: 50px auto;
}

input.search {
  margin: 0;
  text-align: center;
  outline: 0;
  border: 10px solid #F7F7F7;
  width: 120%;
  left: -10%;
  position: relative;
  top: 10px;
  z-index: 2;
  border-radius: 5px;
  font-size: 40px;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.12), inset 0 0 2px rgba(0, 0, 0, 0.19);
}

.suggestions {
  margin: 0;
  padding: 0;
  position: relative;
  /*perspective: 20px;*/
}

.suggestions li {
  background: white;
  list-style: none;
  border-bottom: 1px solid #D8D8D8;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.14);
  margin: 0;
  padding: 20px;
  transition: background 0.2s;
  display: flex;
  justify-content: space-between;
  text-transform: capitalize;
}

.suggestions li:nth-child(even) {
  transform: perspective(100px) rotateX(3deg) translateY(2px) scale(1.001);
  background: linear-gradient(to bottom,  #ffffff 0%,#EFEFEF 100%);
}

.suggestions li:nth-child(odd) {
  transform: perspective(100px) rotateX(-3deg) translateY(3px);
  background: linear-gradient(to top,  #ffffff 0%,#EFEFEF 100%);
}

span.population {
  font-size: 15px;
}

.hl {
  background: #ffc600;
}