Day 6 - Type Ahead

Full Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  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)); //spread

function findMatches(wordToMatch, cities) {
return cities.filter((place) => {
const regex = new RegExp(wordToMatch, 'ig'); // i:insensitive, g:global
return place.city.match(regex) || place.state.match(regex);
});
}

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;
}

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

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

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

window.addEventListener('load', function () {
searchInput.focus();
});

json 데이터 가져오기 (fetch)

1
2
3
fetch(endpoint)
.then((blob) => blob.json())
.then((data) => cities.push(...data)); //spread

fetch()를 호출하면 브라우저는 네트워크 요청을 보내고 Promise가 반환된다. 데이터를 json 형태로 만들고, 그 데이터를 citiespush한다. (citiesconst로 선언되었기 때문에 대입시킬 수 없다)

정규식 사용하기 (RegExp)

데이터 찾기

1
2
3
4
5
6
function findMatches(wordToMatch, cities) {
return cities.filter((place) => {
const regex = new RegExp(wordToMatch, 'ig'); // i:insensitive, g:global
return place.city.match(regex) || place.state.match(regex);
});
}

정규식을 사용하기 위해 RegExp를 선언한다. 자바스크립트에서 정규표현식 사용은 PoiemaWeb에 잘 설명되어 있다. 찾을 단어 wordToMatchcities의 각 요소 place에서 찾는데, citystate에서 한 개라도 맞으면 반환한다.

데이터 변환(교체)하고 HTML에서 나타내기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

입력하는 값과 일치하는 부분을 highlight 표시하기 위해 regex를 이용하여 바꿔준다. 그리고 matchArray의 값을 적절히 가공하여 innerHTML에 넣어준다.

Event 추가하기

1
2
3
4
5
6
7
8
9
const searchInput = document.querySelector('.search');
const suggestions = document.querySelector('.suggestions');

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

window.addEventListener('load', function () {
searchInput.focus();
});

필요한 함수들을 모두 만들었으니 이벤트만 걸어주면 된다. change에만 이벤트를 걸면 사용자가 입력이 완전히 끝났을 때만 함수가 실행되므로 예제의 목적에 맞게 keyup에도 이벤트를 걸어야 실시간으로 변하는 것처럼 보인다.

예제 솔루션에는 포함되어 있지 않지만 focus 메소드를 사용하여 input 객체에 포커스를 주었다.

응용하기

regex 대신 includes 사용하기

1
2
3
4
5
6
7
8
9
10
function findMatches(wordToMatch, cities) {
return cities.filter((place) => {
if (
place.city.toLowerCase().includes(wordToMatch.toLowerCase()) ||
place.city.toLowerCase().includes(wordToMatch.toLowerCase())
) {
return place.city || place.state;
}
});
}

findMatches() 함수를 정규식을 사용하지 않고 includes를 사용할 수도 있다. 이게 더 가독성이 좋을 지도 모르겠다.

Debounce 처리하기

1
2
3
4
5
6
7
8
9
10
let timer;
const delay = 200;
searchInput.addEventListener('input', (e) => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
displayMatches(e.target);
}, delay);
});

모든 input event에서 함수를 처리하면 성능상으로도, 비용상으로도 비효율적이다. 이 때 디바운싱을 사용할 수 있다. 검색 event에서 디바운싱의 사용은 획기적으로 API 요청을 줄여주기에, 안 쓸 이유가 없다. 다른 글에 디바운싱과 쓰로틀링을 정리해두었으니 참고하자.

이제 6번째 날인데, 자바스크립트의 활용 범위는 끝이 없는 것 같다. 남은 24일이 기대된다.