0%

JS地下城-5F全台空氣指標

作品

JS地下城5F-全台空氣指標

全台空氣指標

拆分目標

規劃目標,主要解決 :

  • CORS 抓取 JSON 資料問題
  • 事件綁定
  • 畫面渲染

接著按照目標,再拆分細項目標。

抓取 API 資料

  • 利用 Google App Script 解決CORS問題
  • 不使用套件抓 API,使用 fetch
    • 取得縣市資料
    • 將縣市資料另存,不用再次抓取資料
    • 縣市資料渲染至畫面選單上
    • 若取得資料失敗,則顯示”請重新整理。”

      change 與 click 事件

  • change,更新所選縣市名稱與撈取時間
  • change,將站點的詳細資料渲染至畫面上
  • change,站點名稱渲染至畫面列表上
  • click,點擊站點時,顯示該站點資訊
    • 動態綁定 click 事件
    • 將點擊的站點詳細資料顯示在左方列表,其餘重新排列顯示

      畫面渲染

  • 站點名稱與數值利用格線一左一右顯示
  • 其餘站點照格線欄位排序,由左至右,往下排列
  • 站點詳細資訊與列表,一左一右分別顯示
  • 利用 setInterval() 讓分隔線顯示長度增減,以達到讀取效果

資料處理

取得縣市資料

已知縣市名稱絕對有重複,故先篩選出不重複的資料。

1
2
3
4
5
6
7
8
9
/* 利用 indexOf 篩選出不重複的資料 */
// indexOf 若該值在對象陣列中不存在,則回傳 "-1"
let data = [{A站點}, {B站點}, {C站點}, ...]
let areas = []
date.forEach( item => {
if ( areas.indexOf(item.County) === -1 ) {
areas.push( item.County )
}
})

處理站點資料

節錄一小段程式碼,主要將撈取回來的資料,利用 forEach 與樣板字面值組合成所需字串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let data = [{A站點}, {B站點}, {C站點}, ...]
const render = () => {
let siteDetailStr = ''
let sitesOtherStr = ''
// 將第一筆站點資料作為左側詳細資訊
// shift() 會改變陣列長度,取出第一筆資料後回傳
let siteFirst = data.shift()
siteDetailStr = `<span class="h2 fw-bolder">${siteFirst.SiteName}</span>`
// 將其餘資料作為右側列表
data.forEach( item => {
sitesOtherStr += `
<li>
<span class="h2 fw-bolder">${item.SiteName}</span>
</li>`
})
}
// 最後將取出的資料放回,這邊直接用 push
// 若想放到其他位置也可以使用陣列其他方法
data.push(siteFirst)

分隔線讀取效果

利用 setInterval(),設定好間隔時間,會重複執行內部程式碼,利用閉包的概念,將值變更,直到指定值後,再重新賦予初始值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const dottedLine = document.querySelector('.custom--dottedLine')
const loadingFn = (loading) => {
let x = 1
if (loading) {
setInterval(()=>{
if (x <= 7) {
dottedLine.style.width = `${x++}2%`
} else {
x = 1
}
}, 500)
} else {
clearInterval(x)
dottedLine.style.width = `62%`
}
}

XMLHttpRequest 與 Fetch 獲取 API 資料的差異

兩者都能進行 AJAX 行為,最大的差別在於,處理非同步行為時,其可讀性與維護性,來看以下例子 :

XMLHttpRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* XMLHttpRequest */
const api = "https://randomuser.me/api/"
let data = ''

const req = new XMLHttpRequest()
req.open('GET', api)
req.onload = function (){
if(req.status = 200){
// 成功時
data = req.response
console.log(typeof data)
} else {
// 失敗時
console.log('取得資料失敗')
}
}

req.send()
// "string",未經過處理的 JSON 字串

fetch

第一次取回的 response 是被鎖定的物件,其中一個方法會回傳 JSON格式的資料,轉為 JSON 格式後,再利用下一步去接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* fetch */
const api = "https://randomuser.me/api/"
let data = []

fetch(api).then( response => {
return response.json()
}).then( jsonData => {
// 成功時,使用 then 取得實際資料
data = jsonData
console.log( typeof data )
}).catch( error=>{
// 失敗時,使用 catch 回報錯誤資訊
console.log( '資料有誤,請重新整理。' )
})
// "object",JSON 格式的物件

XMLHttpRequest 的寫法必須使用多個步驟去取得資料,且取得的資料為 JSON 格式的字串,因為伺服器傳輸必須是字串,還需要轉回 JSON 格式的物件,若接著處理該資料,不僅造成不易閱讀,也不好維護。而 fetch 的方式有使用 Promise 處理,可讀性與維護性提升,只要伺服器有回應,不需要額外寫判斷去處理。


使用 Promise 來優化 XMLHttpRequest JAX

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
const api = "https://randomuser.me/api/"
let data = []

function get(url){
return new Promise( (resolve, reject) => {
var req = new XMLHttpRequest()
req.open( 'GET', url )
req.onload = function (){
if( req.status = 200 ){
// 成功時
resolve( req.response )
} else {
// 失敗時
reject( '資料有誤,請重新整理。' )
}
}
req.send()
})
}
get(api).then( response => {
data = response
console.log( typeof data )
}).catch( error => {
console.log(error)
})
// "string"
// 若要測試 reject 有沒有回傳給 catch,可以暫時改寫成
// if( req.status = 200 ){
// reject( '資料有誤,請重新整理。' )
// } else {
// resolve( req.response )
// }

CORS 問題解決方案

主要看後端有沒有設定跨域資料時,權限有沒有開放,不過還是可以透過 Google App Script 來暫時解決。簡易的方式如下 :

  1. 建立一份新的 Google 試算表
  2. 在工具列中找到 “工具” > “指令碼編輯器”
  3. 將預設函式取代為以下程式碼
  4. 接著部屬為 “網頁應用程式”,複製該連結
  5. 在連結後方找到 “exec”,在其後方補上開源資料的網址
    • e.g. https://一連串的亂碼/exec?url=開源資料的網址
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      // 作為中繼站,跨網域存取開源資料
      function doGet(e){
      // 取得傳入 URL
      let url = e.parameter.url

      // 發出 GET 請求,並設定 hearder json
      let response = UrlFetchApp.fetch(decodeURIComponent(url),{
      headers: { "Content-type" : "application/json" }
      })
      // 取得 json 資料
      let data = JSON.parse(response.getContentText());
      return ContentService.createTextOutput(JSON.stringify(data)).setMimeType(ContentService.MimeType.JSON)
      }
      // 若 API 網址無法正確取得資料,將 url 暫時改為開往資料的網址

參考來源

  1. 行政院環境保護署開放資料平台
  2. 空氣品質指標 - JSON
  3. Google Apps Script
  4. pvt5r486 - 了解 e.parameter 是什麼
  5. 從ES6開始的JavaScript學習生活 - AJAX與Fetch API
  6. Vic - Google試算表作為線上資料庫 - 讀取資料