iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
自我挑戰組

JS30 學習日記系列 第 6

Day 6 - Ajax Type Ahead

前言

JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No FrameworksNo CompilersNo LibrariesNo Boilerplate 在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。

另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。


本日目標

利用 Fetch API 取得 JSON 格式的資料,隨著輸入關鍵字的不同,將特定資料篩選出來並呈現在網頁上。


解析程式碼

JS 部分

將要取得的資料來源網址放入 endpoint中,並建立一個空的city陣列。

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

const cities = [];/*empty array*/

fetch()Fetch API 的一個方法,主要用於發出資料的 request 並回傳 Promise,有點類似於XMLHttpRequest,但在使用上較有彈性。

Promise 物件代表的是一個將要完成或失敗的非同步操作,以及它所產生的值。

then() 方法最多需要兩個參數,分別作為 Promise 成功和失敗時的 callback function 並返回 Promise

json() 方法會接收一個 response stream,在讀取完成之後返回一個解析成 JSON 格式的 Promise

我們透過 fetch()endpoint 發送 request,之後將 return 的 Promisejson 格式進行解構,最後將這些資料一筆筆的 push 到空的 cities 陣列中。...data的主要用意在於將原本的 data 陣列中的元素個別拆出來放進 cities 裡面,如果直接寫cities.push(data)data 會變成在 cities 內部的一個陣列型態元素。

fetch(endpoint)
  .then(blob => blob.json())
  .then(data => cities.push(...data));

findMatches() 需要用到兩個參數,wordToMatch 用來傳入要尋找的文字,cities 則是原本的資料陣列,最後回傳經過 filter() 過濾後的 cities 陣列。

filter() 裡,我們將 cities 的每一個元素用 RegExp 物件進行正規表示式的字元比對,之後將包含關鍵字的 city 或是 state回傳。

RegExp() 用來建立一個正規表達式的物件,wordToMatch 是要進行比對的內容,'gi'則是 flag,g 代表搜尋出所有符合比對的文字,而不是比對出第一筆就停止,i 代表比對時不分大小寫。

function findMatches(wordToMatch, cities){
    return cities.filter(place => {
        const regex = new RegExp(wordToMatch,'gi');
        return place.city.match(regex) || place.state.match(regex);
    })
}

numberWithCommas() 主要用來幫我們在每三位數字加上逗號(ex.1,000)。toString() 可以幫我們將 x 轉成字串,之後利用 replace() 進行正規表示式的判斷並取代字元。

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

宣告 matchArray 並放入找到的結果陣列。接著,將陣列中的元素換成 HTML 的格式。再來宣告一個 Regex 物件,然後宣告兩個常數並各自以正規表示式判斷並取代(replace)原有的文字內容,改以 HTML 的方式呈現。

之後,透過 Template literals 的格式傳回一個<li>...</li>。最後,使用 join() 將陣列中的所有元素以空字串為分隔,形成一個很長的字串再放入.suggestions的標籤中。

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="ppulation">${numberWithCommas(place.population)}</span>
            </li> 
          `
    }).join('');
    suggestions.innerHTML = html;
}

分別宣告常數 searchInputsuggestions 並取得 .search.suggestions標籤。最後為搜尋欄(searchInput) 註冊changekeyup事件,當欄位數值發生改變或放開鍵盤的那個剎那都會觸發事件,兩個事件都是以 displayMatches() 進行事件處理。

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

searchInput.addEventListener('change',displayMatches)
searchInput.addEventListener('keyup',displayMatches);
補充資料:

Promise
使用 Fetch
Promise.prototype.then()
Response.json()
Spread syntax (...)
RegExp()
比較 keydown, keypress, keyup 的差異

範例網頁請按此


上一篇
Day 5 - Flex Panels Image Gallery
下一篇
Day 7 - Array Cardio Day 2
系列文
JS30 學習日記30

尚未有邦友留言

立即登入留言