這絕對不是向獵人致敬的標題哦XD是說是不是看過獵人又是一種年紀的象徵
咳咳..... 前兩天的文章中,
拿了 JSON 格式的資料來練習取資料跟應用的感覺,
可是你會說,
我總不能每次都傻傻的去用高級手工藝把資料貼到 Excel 再轉成 JSON 格式,
再把它貼到 JavaScript 宣告一個物件陣列才能用,
這樣不是很麻煩嗎orz 一點都沒有程式自動化的感覺.....
對,沒錯,
所以線上其實很多 API 服務,
當你去跟該 API 問資料,
它也會回你 JSON 或其他的格式資料,
你就可以很方便拿來應用啦:D
事不宜遲,讓我們趕緊動手吧!
雖然現在實名制口罩大家應該都不搶了,
不過前一陣子很多工程師都發揮長才利用這個資料集在做口罩地圖,
所以今天就讓我們來試個吧!
然後現在能搜得到的都是已經用資料集所呈現的口罩地圖,
我之前找到現在還有在更新的資料集在這邊,
看起來好像是好心大大轉過一手放在 GitHub 的? → 口罩數量資料集
在這邊感恩提供資料的大大~~~
如果程式已經寫的很熟練的大大當然不在此限,
只是之前小的在寫這種動態拿資料回來再做處理的,
很常是資料拿到後,後面處理資料的邏輯沒寫好,
但我前面一直存取,
然後就被視為攻擊了擋了XD
(因為 API 通常怕被攻擊都會有某段時間存取次數上限值之類的,
通常都要被擋個 N 分鐘後才能再次去問該 API 問資料回來orz)
為了避免這種事發生,
小的後來都習慣先把該 API 的 JSON 檔拿回來,
先存成物件陣列,
等到後面邏輯都寫好沒問題後,
前面才改成動態問資料。
所以先讓我們到 口罩數量資料集
看看資料長什麼樣子吧!
看起來有藥局的名字、電話、地址,還有最重要的
成人口罩剩餘數 → "mask_adult": 136,
兒童口罩剩餘數 → "mask_child": 433,
然後你也會觀察到每筆資料其實是包在 "features",
因此我們要靜態拿資料時,只要取 "features" 以下就好,最開頭的 "type" 不用取。
先取個 3 筆放到 JavaScript 並宣告:
let maskdata = [{
"type": "Feature",
"properties": {
"id": "5945030094",
"name": "德興藥局",
"phone": "(03)8889408",
"address": "花蓮縣玉里鎮國武里中山路2段58號",
"mask_adult": 136,
"mask_child": 433,
"updated": "2020\/09\/23 20:41:48",
"available": "星期一上午看診、星期二上午看診、星期三上午看診、星期四上午看診、星期五上午看診、星期六上午看診、星期日上午看診、星期一下午看診、星期二下午看診、星期三下午看診、星期四下午看診、星期五下午看診、星期六下午看診、星期日下午看診、星期一晚上看診、星期二晚上看診、星期三晚上看診、星期四晚上看診、星期五晚上看診、星期六晚上看診、星期日晚上看診",
"note": "口罩販售,營業時間,成人口罩200份,兒童口罩20份,售完為止。",
"custom_note": "",
"website": "",
"county": "花蓮縣",
"town": "玉里鎮",
"cunli": "國武里",
"service_periods": "NNNNNNNNNNNNNNNNNNNNN"
},
"geometry": {
"type": "Point",
"coordinates": [
121.315149,
23.333096
]
}
},
// ... 中略 ...
];
然後也是 console.log 來看看:
console.log(`有 ${maskdata.length} 筆資料`);
console.log(`${maskdata[0].name} 還有 ${maskdata[0].mask_adult} 個成人口罩`);
嗯?為什麼顯示 undefined?
再觀察一次資料結構,
哦~原來 name, mask_adult 等資料是被包在 properties 裡面呀!
來改一下:
console.log(`有 ${maskdata.length} 筆資料`);
console.log(`${maskdata[0].properties.name} 還有 ${maskdata[0].properties.mask_adult} 個成人口罩`);
對了!之後如果還有類似的問題發生,
可以仔細觀察看看資料結構長怎樣哦!
那我們今日課題定為要撈出剩餘某數量以上的口罩有哪些好了!
例如這邊我先找出成人口罩 >100 的有哪幾家:
for ( let i=0; i<maskdata.length; i++ ){
if ( maskdata[i].properties.mask_adult > 100 ){
console.log(`${maskdata[i].properties.name} 還有 ${maskdata[i].properties.mask_adult} 個成人口罩`);
}
}
初步判斷出來了,再來要把 100 改成可動態輸入。
要先在 html 放入輸入數字的 input,以及送出的按鈕。
(PS. input 要給予 id,這樣我們才能透過 getElementById 取得元件再做進一步處理)
<input type="number" id="inputMaskNum">
<input type="button" value="送出" id="sendBtn">
再來複習一下過去兩天的內容 getElementById & addEventListener:
const inputNumElement = document.getElementById("inputMaskNum");
const sendBtnElement = document.getElementById("sendBtn");
sendBtnElement.addEventListener("click", getInputValue);
function getInputValue(){
console.log("點到送出按鈕了");
}
再來我們要在點擊送出按鈕時,取得輸入框所輸入的數字:
function getInputValue(){
console.log("點到送出按鈕了");
console.log(`輸入的值為: ${inputNumElement.value}`);
}
但還記得昨天有遇到一個情況,type 的問題,
所以順便檢查一下 input 取到值的 type 是什麼:
console.log(`輸入的值 type 為: ${typeof(inputNumElement.value)}`);
抓到!之後大家都要記得 input 相關的值初始都會是 string 型態,
所以要當數字處理的話記得要轉一手。
然後把前面寫過的邏輯也合在一起試試看:
let inputNum = parseInt(inputNumElement.value);
for ( let i=0; i<maskdata.length; i++ ){
if ( maskdata[i].properties.mask_adult > inputNum ){
console.log(`${maskdata[i].properties.name} 還有 ${maskdata[i].properties.mask_adult} 個成人口罩`);
}
}
當我輸入 700,
真的撈出只口罩剩餘數量大於 700 的藥局,
本日要求大概 87% 達成了,
剩下的就是要將結果呈現在網頁上,
而不是 console.log 而已。
html 再宣告一個元素:
<ol id="filterResult"></ol>
再複習一下 innerHTML 的用法:
const filterResultElement = document.getElementById("filterResult");
let resultHTMLStr = "";
for ( let i=0; i<maskdata.length; i++ ){
if ( maskdata[i].properties.mask_adult > inputNum ){
resultHTMLStr += `<li>${maskdata[i].properties.name} 還有 ${maskdata[i].properties.mask_adult} 個成人口罩</li>`;
}
}
filterResultElement.innerHTML = resultHTMLStr;
好了,看起來很棒!
但不要忘記我們最初的目的,
就是要將最前面資料的地方改成動態問資料回來!
這邊的語法不用背,直接 copy 再改成今日要存取的 URL 即可:
// 拿即時更新的 JSON 檔
let requestURL = "https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json";
let xhr = new XMLHttpRequest();
xhr.open("GET",requestURL,true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.responseType = 'json';
xhr.send();
// onload 是等資料拿到的時候才執行
xhr.onload = function(){
// maskdata 設為拿到的回傳 JSON
let maskdata = xhr.response.features; // 這邊要觀察一下回傳的資料結構長什麼樣子
// *****等拿到資料再初始化頁面
// *****如果沒有這樣寫,則會跟前面同時執行,就會因為沒資料而出錯
console.log(maskdata);
console.log(`有 ${maskdata.length} 筆資料`);
}
不過因為語法還沒有很熟,我還是習慣不要一步到位,
一步一步慢慢來。
這邊確定拿得到資料再繼續往下:
// onload 是等資料拿到的時候才執行
xhr.onload = function(){
// maskdata 設為拿到的回傳 JSON
let maskdata = xhr.response.features;
// *****等拿到資料再初始化頁面
// *****如果沒有這樣寫,則會跟前面同時執行,就會因為沒資料而出錯
console.log(maskdata);
console.log(`有 ${maskdata.length} 筆資料`);
initialData(maskdata);
}
function initialData(maskdata){
sendBtnElement.addEventListener("click", getInputValue);
function getInputValue(){
console.log("-點到送出按鈕了");
console.log(`輸入的值為: ${inputNumElement.value}`);
console.log(`輸入的值 type 為: ${typeof(inputNumElement.value)}`);
let inputNum = parseInt(inputNumElement.value);
let resultHTMLStr = "";
for ( let i=0; i<maskdata.length; i++ ){
if ( maskdata[i].properties.mask_adult > inputNum ){
resultHTMLStr += `<li>位於 ${maskdata[i].properties.county} ${maskdata[i].properties.town} 的 ${maskdata[i].properties.name} 還有 ${maskdata[i].properties.mask_adult} 個成人口罩</li>`;
}
}
filterResultElement.innerHTML = resultHTMLStr;
}
}
本日打完收工!
這邊整理一下比較重要的概念:
有關更多可以玩的 Open Data 可以到 政府資料開放平臺 搜尋,
但裡面很多資料集可能不是那麼方便使用.....orz
因為動態問資料回來做應用是屬於比較進階的寫法了,
我想明天再複習個會比較好~
那就明日再見啦:D