vue
作業任務:
mounted: function() {
const vm = this;
const api = 'https://opendata.epa.gov.tw/api/v1/AQI?%24skip=0&%24top=1000&%24format=json';
// 使用 jQuery ajax
$.get(api)
.then(function(response) {
vm.data = response;
console.log(response)
})
.catch(function(error){
console.log(error);
})
}
注意,AJAX抓取資料必須至少要等到created之後才可以進行,所以抓API的程式碼,需要寫在mounted
或created
裏。
jQuery的get
方法:$.get(url)
jQuery的then
方法,會回傳一個promise物件,第一個function帶出promise物件裏resolved的值,第二個function帶出promise物件裏rejected的值。
你也可以使用catch
方法去接收rejected的值。
抓取API後,需要抓出裏面的所有地區名稱,並放到選單選項裏。注意,因為會有重複的地區的名稱出現,例如高雄市也有高雄市 - 前金、高雄市 - 左營等等,所以需要抓取唯一值,避免重複。
利用new Set()
的方法就可取得唯一值,因為Set
物件只容許唯一值,不能有重複的值。之後再利用展開運算子把它轉為陣列。
注意,展開運算子(spread)只能展開可以被迭代的東西,物件本身是不能被迭代,因此無法使用展開運算子,而Set是可以被迭代的:
所以可以寫成這樣:
// API 來源
// https://opendata.epa.gov.tw/Data/Contents/AQI/
var app = new Vue({
el: '#app',
data: {
data: [],
starList: [],
filter: '',
},
// 請在此撰寫 JavaScript
computed: {
filterCounty: function() {
const allCounty = this.data.map( item => item.County);
// Set 物件只會容許儲存單一值
// 用展開運算子,把Set物件轉為陣列
return new Set([...allCounty]);
}
},
mounted: function() {
const vm = this;
const api = 'https://opendata.epa.gov.tw/api/v1/AQI?%24skip=0&%24top=1000&%24format=json';
// 使用 jQuery ajax,抓取API
$.get(api)
.then(function(response) {
vm.data = response;
console.log(response)
})
.catch(function(error){
console.log(error);
})
}
});
之後在select用v-for
把地區的唯一名稱印出來:
<div id="app">
<select name="county" id="county" class="form-control mb-3">
<option value="" disabled selected>--- 請選擇城市 ---</option>
<option v-for="county in filterCounty"> {{ county }} </option>
</select>
...
</div>
抓到所有資料後,把所有資料當一個card
元件來呈現,裏面放滿需要顯示的資料,例如地區名稱、AQI指數等等。之前在該card
元件上使用v-for
迭代資料,把一個個card渲染出來。
注意,使用v-for
時需要綁上key
值,key
值需要是唯一值,下文會再詳細講解這部分:
<div id="app">
...
<div class="card-columns">
<card v-for="item in data" :air-quality-data="item" :key="item.SiteId"></card>
</div>
</div>
建立template:
<script type="text/x-template" id="cardTemplate"></script>
註冊元件:
Vue.component('card',{
template: '#cardTemplate',
props: ['airQualityData']
})
結果:
首先在select的標籤用v-model
綁定外層資料filter
:
<select name="county" id="county" class="form-control mb-3" v-model="filter">
...
</select>
然後按filter
的名稱,在computed
裏建立函式,過濾資料:
var app = new Vue({
el: '#app',
data: {
data: [],
filter: '',
},
computed: {
filterCounty: function() {
const allCounty = this.data.map( item => item.County);
// Set 物件只會容許儲存單一值
// 用展開運算子,把Set物件轉為陣列
return new Set([...allCounty]);
},
filterData: function() {
const vm = this;
// 剛載入畫面後,預設顯示所有資料
if(this.filter === '') {
return this.data
// 按filter的地區名稱去撈資料
}else{
return this.data.filter( item => item.County === vm.filter)
}
}
},
mounted: function() {
...
}
});
之前我們是在data裏撈資料顯示,現在改為在filterData裏撈資料:
<div id="app">
<select name="county" id="county" class="form-control mb-3" v-model="filter">
<option value="" disabled selected>--- 請選擇城市 ---</option>
<option v-for="county in filterCounty" :value="county"> {{ county }} </option>
</select>
<div>
<h4>關注城市</h4>
...
</div>
<hr>
<div class="card-columns">
<!-- 在filterData 裏撈資料 -->
<card v-for="item in filterData" :air-quality-data="item" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
取每筆資料中的Status
屬性去做判斷,決定該card需要什麼背景顏色。
Vue.component('card',{
template: '#cardTemplate',
props: ['airQualityData'],
computed: {
statusColor: function() {
switch (this.airQualityData.Status) {
case '良好':
return '';
case '普通':
return 'status-aqi2';
case '對敏感族群不健康':
return 'status-aqi3';
case '對所有族群不健康':
return 'status-aqi4';
case '非常不健康':
return 'status-aqi5';
case '危害':
return 'status-aqi6';
}
},
}
});
Template裏動態綁定class:
<script type="text/x-template" id="cardTemplate"></script>
結果:
要做的功能:
在card元件裏寫click事件,並從內層把該城市的SiteName資料一併帶出去外層:
<script type="text/x-template" id="cardTemplate"></script>
在內層的starEmit會觸發外層事件star-list,再觸發外層的editStarList函式。當觸發內層的starEmit時,會一併把該資料的SiteName帶出去外層(因為之後需要SiteName去做判斷)。
注意,要讀取SiteName的資料,需要寫成this.airQualityData.SiteName
,因為早前我們在外層的card元件裏,把airQualityData
綁定了v-for="item in filterData"
裏的item,這個item就是每一筆被迭代的資料。
Vue.component('card',{
template: '#cardTemplate',
props: ['airQualityData'],
computed: {
statusColor: function() {
switch (this.airQualityData.Status) {
case '良好':
return '';
case '普通':
return 'status-aqi2';
case '對敏感族群不健康':
return 'status-aqi3';
case '對所有族群不健康':
return 'status-aqi4';
case '非常不健康':
return 'status-aqi5';
case '危害':
return 'status-aqi6';
}
},
},
methods: {
//把SiteName資料傳去外層
starEmit: function() {
this.$emit('star-list',this.airQualityData.SiteName);
}
}
});
外層card元件觸發editStarList:
<div id="app">
...
<div class="card-columns">
<card v-for="item in filterData" :air-quality-data="item" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
editStarList函式:
當按下星星,就會把該城市的SiteName加到starSiteName裏。
在加入之前,會做if else判斷,避免重複加入城市。利用indexOf()
去檢查在starSiteName陣列裏是否已經有該SiteName,如沒有就新增,如有就刪除(因為當再次點選星星,就代表想要在關注城市裏移除該城市)。
var app = new Vue({
el: '#app',
data: {
data: [],
// 預設是撈取存在localStorage的資料
starSiteName: JSON.parse(localStorage.getItem('AQIstarList')) || [],
filter: '',
},
// 請在此撰寫 JavaScript
computed: {
filterCounty: function() {
const allCounty = this.data.map( item => item.County);
// Set 物件只會容許儲存單一值
// 用展開運算子,把Set物件轉為陣列
return new Set([...allCounty]);
},
filterData: function() {
const vm = this;
// 剛載入畫面後,預設顯示所有資料
if(this.filter === '') {
return this.data
// 按filter的地區名稱去撈資料
}else{
return this.data.filter( item => item.County === vm.filter)
}
},
starList: function() {
const vm = this;
return this.data.filter( item => vm.starSiteName.indexOf(item.SiteName) !== -1);
}
},
methods: {
editStarList: function(siteName) {
// 判斷是否有重複的site name,如沒有就加入,如有就刪除該筆城市資料
if(this.starSiteName.indexOf(siteName) === -1){
this.starSiteName.push(siteName);
}
else {
this.starSiteName.splice(this.starSiteName.indexOf(siteName), 1);
}
localStorage.setItem('AQIstarList', JSON.stringify(this.starSiteName));
}
},
mounted: function() {
const vm = this;
const api = 'https://opendata.epa.gov.tw/api/v1/AQI?%24skip=0&%24top=1000&%24format=json';
// 使用 jQuery ajax
$.get(api)
.then(function(response) {
vm.data = response;
console.log(response)
})
.catch(function(error){
console.log(error);
})
}
});
只把SiteName加進陣列是不夠的,因為還要把整筆資料顯示出來,所以我們要憑著在starSiteName裏的SiteName,取回一筆筆完整的資料。
在computed裏,建立starList,透過使用filter
和indexOf
方法,過濾data裏的資料,如果該筆資料的SiteName是存在在starSiteName
裏(即是indexOf不會等於-1),就回傳該筆資料:
var app = new Vue({
el: '#app',
data: {
data: [],
starSiteName: JSON.parse(localStorage.getItem('AQIstarList')) || [],
filter: '',
},
// 請在此撰寫 JavaScript
computed: {
filterCounty: function() {
const allCounty = this.data.map( item => item.County);
// Set 物件只會容許儲存單一值
// 用展開運算子,把Set物件轉為陣列
return new Set([...allCounty]);
},
filterData: function() {
const vm = this;
// 剛載入畫面後,預設顯示所有資料
if(this.filter === '') {
return this.data
// 按filter的地區名稱去撈資料
}else{
return this.data.filter( item => item.County === vm.filter)
}
},
starList: function() {
const vm = this;
// 如果該筆資料的SiteName是存在在starSiteName裏,就回傳該筆資料
return this.data.filter( item => vm.starSiteName.indexOf(item.SiteName) !== -1);
}
},
methods: {
editStarList: function(siteName) {
// 判斷是否有重複的site name,如沒有就加入,如有就刪除該筆城市資料
if(this.starSiteName.indexOf(siteName) === -1){
// console.log('index:' + this.starSiteName.indexOf(item));
this.starSiteName.push(siteName);
}
else {
// console.log('index:' + this.starSiteName.indexOf(item));
this.starSiteName.splice(this.starSiteName.indexOf(siteName), 1);
}
localStorage.setItem('AQIstarList', JSON.stringify(this.starSiteName));
}
},
mounted: function() {
...
}
});
之後starList
就會是一筆筆完整的資料,這時候在「關注城市」的card元件裏,改為在starList
裏,把資料印出來:
<div id="app">
<select name="county" id="county" class="form-control mb-3" v-model="filter">
<option value="" disabled selected>--- 請選擇城市 ---</option>
<option v-for="county in filterCounty" :value="county"> {{ county }} </option>
</select>
<div>
<h4>關注城市</h4>
<div class="card-columns">
<!-- 改在starList 裏撈資料 -->
<card v-for="item in starList" :air-quality-data="item" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
<hr>
<div class="card-columns">
<card v-for="item in filterData" :air-quality-data="item" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
結果:
這個功能有兩個做法都能做到,一是使用<slot>
,直接把在template中的空心星星替換成實心星星。
二是動態綁定class,透過判斷該筆資料的SiteName目前是否在外層資料的starSiteName,來決定該icon是要綁上實心星星還是空心星星的class名稱。
這裏示範第二個做法:
Vue.component('card',{
template: '#cardTemplate',
// 新增一個叫starSiteNameList的props
// 用來連接外層資料的starSiteName
props: ['airQualityData','starSiteNameList'],
computed: {
statusColor: function() {
switch (this.airQualityData.Status) {
case '良好':
return '';
case '普通':
return 'status-aqi2';
case '對敏感族群不健康':
return 'status-aqi3';
case '對所有族群不健康':
return 'status-aqi4';
case '非常不健康':
return 'status-aqi5';
case '危害':
return 'status-aqi6';
}
},
addStarIcon: function() {
// 判斷這筆資料的SiteName是否在外層資料starSiteName裏面
// 如果是,就用實心星星的class
return this.starSiteNameList.indexOf(this.airQualityData.SiteName) === -1 ? 'far fa-star' : 'fas fa-star'
}
},
methods: {
starEmit: function() {
this.$emit('star-list',this.airQualityData.SiteName);
}
}
});
在template裏動態綁定addStarIcon所回傳的值:
<script type="text/x-template" id="cardTemplate"></script>
別忘記在card元件裏,需要定義剛才所新增的props,starSiteNameList,它需要連接外層資料starSiteName:
<div id="app">
<select name="county" id="county" class="form-control mb-3" v-model="filter">
<option value="" disabled selected>--- 請選擇城市 ---</option>
<option v-for="county in filterCounty" :value="county"> {{ county }} </option>
</select>
<div>
<h4>關注城市</h4>
<!-- 定義starSiteNameList -->
<div class="card-columns">
<card v-for="item in starList" :air-quality-data="item" :star-site-name-list="starSiteName" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
<hr>
<div class="card-columns">
<!-- 定義starSiteNameList -->
<card v-for="item in filterData" :air-quality-data="item" :star-site-name-list="starSiteName" :key="item.SiteId" @star-list="editStarList"></card>
</div>
</div>
結果:
上面的結果可見,當一筆資料被加進關注城市後,下方位置仍然會重複顯示該筆資料,如果要做到不重複顯示,我們就需要修改filterData:
computed: {
...,
filterData: function() {
const vm = this;
// 剛載入畫面後,預設顯示所有資料
if(this.filter === '') {
// 不顯示已被加進關注城市的城市
return this.data.filter( item => vm.starSiteName.indexOf(item.SiteName) === -1 )
// 按filter的地區名稱去撈資料
}else{
const filterList = this.data.filter( item => item.County === vm.filter)
// 不顯示已被加進關注城市的城市
return filterList.filter( item => vm.starSiteName.indexOf(item.SiteName) === -1 )
}
},
...
}
結果: