寫在前頭
這個系列總算來到尾聲,今天我們會把口罩地圖的剩下 JavaScript 的部分講完,主要功能部分就完成了,剩下的就是漫長的 RWD 調整過程,有時間的話也許再開一篇純粹講解 CSS 及一些 UI 設計的想法,但這算是番外篇了,如果等不及的朋友,也可以直接參考我的 GitHub 觀看 CSS 的部分,接下來就讓我們一起迎接本系列的倒數第 2 篇~(灑花)
本篇記錄,會講到搭配 select 的改變,把相對應的藥局清單,渲染在畫面列表中,及點選列表中的藥局,即會定位到地圖上該藥局的位置,並展開 Popup。
讓藥局列表渲染在左側 panel 下半部
首先我們先看完整的程式碼:
function renderList(town,county){
let str = '';
for(let i = 0;i<data.length;i++){
const countyName = data[i].properties.county;
const townName = data[i].properties.town;
const pharmacyName = data[i].properties.name;
const maskAdult = data[i].properties.mask_adult;
const maskChild = data[i].properties.mask_child;
const pharmacyAddress = data[i].properties.address;
const pharmacyPhone = data[i].properties.phone;
const pharmacyNote = data[i].properties.note;
let maskAdultJudge;
let maskChildJudge;
if (maskAdult >= 100) {
maskAdultJudge = 'bg-sufficient';
} else if (maskAdult < 100 && maskAdult !== 0) {
maskAdultJudge = 'bg-insufficient';
} else {
maskAdultJudge = 'bg-none';
}
if (maskChild >= 100) {
maskChildJudge = 'bg-sufficient';
} else if (maskChild < 100 && maskChild !== 0) {
maskChildJudge = 'bg-insufficient';
} else {
maskChildJudge = 'bg-none';
}
if(countyName == county && townName == town){
str+=`<ul class="maskContent">
<div class="pharmacyTitle" data-lat="${data[i].geometry.coordinates[1]}" data-lng="${data[i].geometry.coordinates[0]}">
<li data-name="${pharmacyName}"><span>${pharmacyName}</span></li>
<p class="infoText"><i class="fas fa-map-marker-alt"></i> ${pharmacyAddress}</p>
<p class="infoText"><i class="fas fa-phone-square-alt"></i> ${pharmacyPhone}</p>
<p class="noteText"> ${pharmacyNote}</p>
<div class="panelMaskNum" data-name="${pharmacyName}">
<div class="${maskAdultJudge}">
<div class="infoLayout">
<img class="adultIcon" src="" alt="">
<p>${maskAdult}</p>
</div>
</div>
<div class="${maskChildJudge}">
<div class="infoLayout">
<img class="kidIcon" src="" alt="">
<p>${maskChild}</p>
</div>
</div>
</div>
</div>
</ul>`
}
}
document.querySelector('.pharmacyList').innerHTML = str;
var pharmacyTitle = document.querySelectorAll('.pharmacyTitle');
var pharmacyNameList = document.querySelectorAll('.maskContent');
clickPharmacyGeo(pharmacyTitle, pharmacyNameList);
}
這些內容在之前也有寫過一次,非常相似的,接著我們分段來看:
// 先宣告 str 為空字串 ,待會取得資料後,會使用 str 組字串渲染到畫面上
let str = '';
// 使用 for 迴圈取出 data 裡的資料
for(let i = 0;i<data.length;i++){
// 宣告變數,並將資料賦值到變數上
const countyName = data[i].properties.county;
const townName = data[i].properties.town;
const pharmacyName = data[i].properties.name;
const maskAdult = data[i].properties.mask_adult;
const maskChild = data[i].properties.mask_child;
const pharmacyAddress = data[i].properties.address;
const pharmacyPhone = data[i].properties.phone;
const pharmacyNote = data[i].properties.note;
let maskAdultJudge;
let maskChildJudge;
// 使用判斷式,判斷口罩數量,回傳相對應的樣式設定
if (maskAdult >= 100) {
maskAdultJudge = 'bg-sufficient';
} else if (maskAdult < 100 && maskAdult !== 0) {
maskAdultJudge = 'bg-insufficient';
} else {
maskAdultJudge = 'bg-none';
}
if (maskChild >= 100) {
maskChildJudge = 'bg-sufficient';
} else if (maskChild < 100 && maskChild !== 0) {
maskChildJudge = 'bg-insufficient';
} else {
maskChildJudge = 'bg-none';
}
// 接著再用判斷式來篩選資料,如果縣市名相同且鄉鎮區名也相同,那就把資料累加到 str
if(countyName == county && townName == town){
// 字串累加方式使用 ES6 的樣板字面值,可以更直覺的組出 HTML 結構
str+=`<ul class="maskContent">
<div class="pharmacyTitle" data-lat="${data[i].geometry.coordinates[1]}" data-lng="${data[i].geometry.coordinates[0]}">
<li data-name="${pharmacyName}"><span>${pharmacyName}</span></li>
<p class="infoText"><i class="fas fa-map-marker-alt"></i> ${pharmacyAddress}</p>
<p class="infoText"><i class="fas fa-phone-square-alt"></i> ${pharmacyPhone}</p>
<p class="noteText"> ${pharmacyNote}</p>
<div class="panelMaskNum" data-name="${pharmacyName}">
<div class="${maskAdultJudge}">
<div class="infoLayout">
<img class="adultIcon" src="" alt="">
<p>${maskAdult}</p>
</div>
</div>
<div class="${maskChildJudge}">
<div class="infoLayout">
<img class="kidIcon" src="" alt="">
<p>${maskChild}</p>
</div>
</div>
</div>
</div>
</ul>`
}
}
接著看這一段程式碼的最後一部分:
// 最後將累加的字串,用 innerHTML 塞進 panel 下半部的 .pharmacyList
document.querySelector('.pharmacyList').innerHTML = str;
// 宣告變數,來選取 DOM 元素,這裡由於有許多個同名的元素,所以要使用 querySelectorAll
var pharmacyTitle = document.querySelectorAll('.pharmacyTitle');
var pharmacyNameList = document.querySelectorAll('.maskContent');
// 然後帶入我們要講的最後一個函式
clickPharmacyGeo(pharmacyTitle, pharmacyNameList);
此時畫面雖然有些怪異,但其實已經可以正常運作:
只要選取縣市及鄉鎮區,就可以正確顯示了。
接著,我們在 JavaScript 再加上這一段:
function getData(){
const xhr = new XMLHttpRequest;
xhr.open('get','https://raw.githubusercontent.com/kiang/pharmacies/master/json/points.json',true)
xhr.send(null);
xhr.onload = function(){
data = JSON.parse(xhr.responseText).features;
addMarker();
// 增加預設載入的鄉鎮區及縣市,就不會有上面列表空白的情況產生了
renderList('永康區','臺南市');
addCountyList();
}
}
成果如下:
點選列表中的藥局,地圖會定位至該藥局
接下來是最後一個函式:
function clickPharmacyGeo(pharmacyTitle, pharmacyNameList){
// 使用 for 迴圈取出 pharmacyNameList 裡的資料
for(let i=0;i<pharmacyNameList.length;i++){
// 對每一個 pharmacyTitle 進行監聽
pharmacyTitle[i].addEventListener('click',function(e){
//宣告變數,並把經緯度賦值到變數上
Lat = Number(e.currentTarget.dataset.lat);
Lng = Number(e.currentTarget.dataset.lng);
// map 取得經緯度,並設定 zoom in 數值為 20,這會關係到 marker 的 Popup
能不能自動展開,所以請依自己的需求設定 zoom in 的數值
map.setView([Lat, Lng], 20);
// 對 markers 這個 group 使用 eachLayer 的方法,取得每一個layer
(marker)
markers.eachLayer(function (layer) {
// 宣告變數,並使用 getLatLng 的方法,取得 layer(marker) 的經緯
度,並賦值到變數上
const layerLatLng = layer.getLatLng();
// 用判斷式比對經緯度,如果 layer(marker)的經緯度與點擊目標的經
緯度相同,則展開 Popup
if (layerLatLng.lat == Lat && layerLatLng.lng == Lng) {
layer.openPopup();
}
});
})
}
}
至此,我們已完成口罩地圖的基本功能了!最後,我們來稍微美化一下藥局的列表。
初步美化藥局列表
我們先在 HTML 的 <head>
裡引用 Font Awesome:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.0-12/css/all.min.css"/>
接著,我們在 CSS 裡加上這一段:
.pharmacyList{
height: 50%;
overflow:auto;
}
.pharmacyTitle{
width:90%;
}
.pharmacyList li{
font-size: 22px;
font-weight: 900;
color:#072e00;
border-left:solid 3.5px #072e00;
}
.pharmacyList li span{
padding-left: 8px;
}
.pharmacyList ul{
padding:20px 0 20px 0;
display: flex;
justify-content: center;
border-bottom: solid 0.5px #072e00;
}
.infoText{
color: #072e00;
padding-top: 15px;
}
.noteText{
color: black;
padding-top: 12px;
font-size: 12px;
word-break: normal;
}
.maskContent:hover{
background:#dfdfdf;
cursor: pointer;
}
.panelMaskNum{
padding: 20px 0 10px 0;
font-size: 20px;
display:flex;
justify-content: center;
}
.infoLayout{
display: flex;
justify-content: center;
align-items: center;
}
初步的美化,就完成了,成果如下:
資料補充
- Leaflet API reference
LayerGroup Methods:eachLayer( fn, context?)
https://leafletjs.com/reference-1.6.0.html#layergroup-eachlayer
Marker Methods:getLatLng()
https://leafletjs.com/reference-1.6.0.html#marker-getlatlng