[攻略前言]
原本以為經過 4F 的洗禮後,
這關應該不會很難,
殊不知我還是遇到了幾個問題orz
因為 4F 禁用套件要用 JavaScript 原生語法寫,
攻略 5F 想說要重回 Vue 的懷抱,
然後就有種近鄉情怯(?)的感覺XD"
[攻略過程]
其實更貼切的形容我攻略 5F 的感想,在這之前用得很順手的武器,在 4F 被強迫不能用,
5F 拿回武器後,一開始有種用起來不順手的感覺XD
然後我(剛開始)看到題目覺得 5F JavaScript 的部份不難,
是難在樣式,
攻略 5F 的過程分成以下 4 個階段:
1. 先用一筆假資料在 html 上,把樣式手刻出來。
2. 樣式完成後,開始要寫 JavaScript 的部份前,先用變數寫死資料(數筆),再用假資料將主要的幾個邏輯刻出來。
3. JavaScript 的邏輯差不多了,就將資料的部份改成動態取 json 資料
4. 將 XMLHttpRequest 改成 Fetch (Promise)
以下再分別說明這 4 個階段分別遇到的問題。
1. 先用一筆假資料在 html 上,把樣式手刻出來。
1) 中文及英文/數字好看的字型不一樣
我很習慣網頁全用微軟正黑體,
但在英文/數字的呈現上就沒那麼好看了,
於是找到中文、英文/數字可以分開訂字型的語法~~~
(PS. 覺得找到卡斯伯老師的文章機率很高XD 感恩卡斯伯老師XD)
主要是用 英文跟中文分佈的 unicode-range 範圍不一樣,
因此可以達到分開訂定,
CSS語法如下:
/* Latin characters 專用 */
@font-face {
font-family: MyCustomFont; /* 同樣的 font-family */
unicode-range: U+00-024F; /* 英語系為U+00-024F */
src: local("Segoe UI");
}
/* 中文專用 */
@font-face {
font-family: MyCustomFont; /* 同樣的 font-family */
unicode-range: U+4E00-9FFF; /* 中文的語系的unicode-range為U+4E00-9FFF */
src: local("微軟正黑體");
}
body{
font-family: MyCustomFont; /* 在這裡就寫自訂的字型名稱 */
}
2) 畫線請愛用偽元素
我記得這是很久以前 RWD 的最終作業,
葉子助教大大給我的建議,
然後就一直謹記到現在XD
(雖然寫在這邊葉子助教大大好像不會看到,但還是要特別感謝一下XD)
.title{
&::after{
// 偽元素的線
content: "";
flex-grow: 1;
border-top: 3px dotted #000000;
margin: 0px 10px;
}
}
3) 子選單要粘在主選單下,且點選主選單才要顯示子選單
且子選單顯示的時候,要覆蓋在下面的主選單之上。
這就要追朔到之前 HTML 課程學到的了,
主選單要用相對定位,
.item{
position: relative; // 相對定位
}
子選單要用絕對定位。
.displaySubInfo{ // 地區的細項
position: absolute; //絕對定位
z-index: 2;
top: 63px; // 以主選單定位子選單的座標
}
2. 樣式完成後,開始要寫 JavaScript 的部份前,先用變數寫死資料(數筆),再用假資料將主要的幾個邏輯刻出來。
在 JavaScript 使用的變數:
aqiData: [{"SiteName":"彰化新庄","County":"彰化縣","AQI":"","Pollutant":"","Status":"設備維護","SO2":"","CO":"","CO_8hr":"","O3":"","O3_8hr":"","PM10":"47","PM2_5":"","NO2":"","NOx":"","NO":"","WindSpeed":"2.5","WindDirec":"251","PublishTime":"2020-05-04 13:00","PM2_5_AVG":"","PM10_AVG":"46","SO2_AVG":"","Longitude":"","Latitude":"","SiteId":""}],
1) 為什麼不一開始就拿動態取得的資料呢?
因為這邊還在寫邏輯,一直為了確認 JavaScript 的邏輯是對是錯而不斷存取 API,
這樣我覺得不是很好,
而且搞不好還會被視為攻擊XD
有些 API 也會限制一段時間內存取的次數,
像是之前在寫 Vue 作業,
會用到 Github repository 的 API,
所以好習慣先用假資料,
等到邏輯都差不多了,
再改成動態存取 API 得到的資料就好。
2) 這時候就要將 html 全部改成 Vue 的寫法
發現 {{item.PM2.5}} 這樣會有error,
我想應該是因為 . 拿來取用屬性或資料用,
所以我先將 PM2.5 改成 PM2_5 就沒問題了。
3) 只有 ul, li 支援 v-for 寫法
可是有下拉式選單跟表格等也想用 v-for 怎麼辦?
外面記得包 template 就可以使用了~
<template v-for="(item,index) in countyData">
<option :value="index">{{item.County}} ({{item.Count}})</option>
</template>
3) Vue 中要知道是第幾筆資料:index
在一般的 JavaScript 第幾筆資料往往要用 for 迴圈 ( var i=0; i < inputArray.length; i++)
然後用 i 判斷是第幾筆資料。
而 Vue 中用 index 就可以在 html 上知道這是第幾筆資料,
真是方便XD
所以我就可以直接用 index 在 value 埋值。
<li class="item" :value="index">
3. JavaScript 的邏輯差不多了,就將資料的部份改成動態取 json 資料
4. 將 XMLHttpRequest 改成 Fetch (Promise)
其實我一開始還是用 XMLHttpRequest 的方法,
用完才發現這樓的要求有:
- 你攻略此 BOSS 的攻略過程心得
- 底層 XMLHttpRequest、Fetch API 的差異
- 使用 Promise 來優化 XMLHttpRequest JAX
- 探討 CORS 問題解決方案
1) 這邊先貼一下原本 XMLHttpRequest 的寫法:
let self = this; // 這行一定要加,因為onload的this不是Vue原本的this
let requestURL = "https://opendata.epa.gov.tw/api/v1/AQI?%24skip=0&%24top=1000&%24format=json"; // 空氣品質指標(AQI) JSON 檔
let xhr = new XMLHttpRequest();
xhr.open("GET",requestURL,true);
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //post最常用的,告訴對方要傳怎樣的格式過去
xhr.responseType = "json";
xhr.send();
xhr.onload = function(){ // onload 是等資料拿到的時候才執行
let preAqiData = xhr.response;
self.processData(preAqiData);
}
2) 然後我就乖乖去找什麼是 fetch 了orz
先來看最簡單的 fetch 寫法:
fetch('https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json')
.then(response => response.json())
.then(myJson=> console.log(myJson));
哇哦,好簡潔,
先使用 Fetch 發送請求 ( request ),
.then 就是要等到前一句完成才會繼續往下執行,
因此這邊會等到拿到回應才繼續執行。
3) 那 Fetch 跟 Promise 有什麼關係呢?
其實 Fetch 屬於 Promise 的物件,
.then 的寫法就是 Promise 來的。
這邊可以參考卡斯伯老師寫的:JavaScript Promise 全介紹
4) 底層 XMLHttpRequest、Fetch API 的差異
因為 2 個寫法都有碰到,
所以秉著實驗的精神,
試了一下這 2 種寫法到底有何不同。
情境:故意將要 fetch 的網址 key 錯
http://raw.githubusercontent.com/kiang/pharmacies/master/json/points.jso (最後少一個n)
Fetch 寫法:
fetch(myRequest)
.then(function(response) {
console.log(response);
if (!response.ok) {
throw new Error(response.statusText);
} else{
console.log(response);
return response;
}
})
.then(responses => responses.json())
.then(myJson=> console.log(myJson))
// 失敗的行為一律交給了 catch
.catch(fail => {
console.log(fail);
})
XMLHttpRequest 寫法:
// 拿即時更新的 JSON 檔
try {
xhr.send();
xhr.onload = function(){
console.log(xhr.response);
}
} catch (error) {
console.log(error);
}
一樣都有 404 error,
但 Fetch 拿得到回應,
但 XMLHttpRequest 拿不到回應,
所以用 Fetch 可以再針對拿到回應的錯誤做處理~
5) CORS 的問題
其實我這次沒遇到XD
但我想到之前上 JavaScript 課程有遇過,
所以就故意用以下這個 url 試了一下:
會有以下 error:
Access to XMLHttpRequest at 'http://opendata.epa.gov.tw/webapi/Data/RainTenMin/?$orderby=PublishTime%20desc&$skip=0&$top=1000&format=json' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
=>沒有開放跨網域抓取資料
那如果真的遇到資料沒有開放跨網域抓取資料怎麼辦呢?
幸好網路很多神人大大已有解法XD
我也是採用 cors-anywhere 的解法~
(其實也是有偷瞄到攻略這樓的其他大大用 cors-anywhere 解法XD)
所以結合上面的 Fetch,
我也把寫法改了一下:
1.先去 fetch 原始 url
2.如果該 url 遇到問題,catch error,
就要改成去 fetch ${cors}${url}
let cors = 'https://cors-anywhere.herokuapp.com/'; // use cors-anywhere to fetch api data
let url = 'http://opendata.epa.gov.tw/webapi/Data/RainTenMin/?$orderby=PublishTime%20desc&$skip=0&$top=1000&format=json'; // origin api url
let myRequest = new Request(`${url}`, myInit);
fetch(myRequest)
.then(function(response) {
console.log(response);
if (!response.ok) {
throw new Error(response.statusText);
} else{
return response;
}
})
.then(responses => responses.json())
.then(myJson=> console.log(myJson))
.catch(fail => { // 失敗的行為一律交給了 catch
console.log(fail);
myRequest = new Request(`${cors}${url}`, myInit);
fetch(myRequest)
.then(responses => responses.json())
.then(myJson=> console.log(myJson))
})
收工XD
(不過可能太多人用 cors-anywhere,
發現有時候會遇到 error。)
※同場加映:加 loading 動畫
其實我覺得這已經變成這樓的必備要求之一了XD這次的頁面是根據 fetch 後的資料去處理及渲染,
因此在拿到資料前畫面會呈現空白狀態,
在拿到資料前要有 loading 過場動畫。
[Day 06]loader.css - 就算loading中,也要很美觀才行
然後我最後用 loader.css 去用了,
html:
<div id="loading"> <!-- loading 動畫元件 -->
<h2>空氣品質指標資料讀取中</h2>
<div class="ball-pulse">
<div></div>
<div></div>
<div></div>
</div>
<div class="ball-pulse">
<div></div>
<div></div>
<div></div>
</div>
</div
CSS:
#loading{
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
position: fixed;
z-index: 100;
top: 0;
}
等到拿到資料,才把它隱藏,
所以在 JavaScript 寫:
.then(function() {
loading_el.style.display = "none"; // 拿到資料就將 loading 動畫隱藏
})



沒有留言:
張貼留言