(Complete guide, from Novice to Ninja)
Learning Udemy Course:Modern JavaScript
建立者:The Net Ninja (Shaun Pelling)
Learn Modern JavaScript from the very start to ninja-level & build awesome JavaScript applications.
您會學到
- Learn how to program with modern JavaScript, from the very beginning to more advanced topics
- Learn all about OOP (object-oriented programming) with JavaScript, working with prototypes & classes
- Learn how to create real-world front-end applications with JavaScript (quizes, weather apps, chat rooms etc)
- Learn how to make useful JavaScript driven UI components like popups, drop-downs, tabs, tool-tips & more.
- Learn how to use modern, cutting-edge JavaScript features today by using a modern workflow (Babel & Webpack)
- Learn how to use real-time databases to store, retrieve and update application data
- Explore API’s to make the most of third-party data (such as weather information)
Project – Weather App
Project Preview & Setup
操作步驟
- 創建一個專案資料夾 weather_app
- 新增一個 index.html 檔案
- index.html 使用 doc + tab 建立環境、載入 CSS、Bootstrap CDN
- 新增一個 scripts 資料夾、並建立 app.js、forecast.js 檔案
- 在 index.html 載入 JS
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Weather</title>
</head>
<body>
<script src="scripts/forecast.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
HTML & CSS Template
資源
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Weather</title>
</head>
<body>
<div class="container my-5 mx-auto">
<h1 class="text-muted text-center my-4">Ninja Weather</h1>
<form class="change-location my-4 text-center text-muted">
<label for="city">Enter a location for weather information</label>
<input type="text" name="city" class="form-control p-4">
</form>
<div class="card shadow-lg rounded">
<img src="https://via.placeholder.com/400x300" class="time card-img-top" alt="">
<div class="icon bg-light mx-auto text-center">
<!-- icon -->
</div>
<div class="text-muted text-uppercase text-center details">
<h5 class="my-3">City name</h5>
<div class="my-3">Weather condition</div>
<div class="display-4 my-4">
<span>temp</span>
<span>°C</span>
</div>
</div>
</div>
</div>
<script src="scripts/forecast.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// style.css
body{
background: #eeedec;
letter-spacing: 0.2em;
font-size: 0.8em;
}
.container{
max-width: 400px;
}
Placeholder Image
AccuWeather API
資源
API Reference
- Locations API
- Text Search – City Search
- Current Conditions API
- Current Conditions – Current Conditions
個人練習
- 政府資料凱放平臺
- 中央氣象局開放資料平臺之資料擷取 API
Get City API Call
// forecast.js
const key = 'rdec-key-123-45678-011121314';
const getCity = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
const query = `?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data);
// console.log(data.records.location[0]);
return data.records.location[0];
};
getCity('臺南市')
.then(data => console.log(data))
.catch(err => console.log(err));
Get Weather API Call
// forecast.js - 1
const key = 'rdec-key-123-45678-011121314';
// get weather information
const getWeather = async (id = 'F-C0032-001') => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/';
const query = `${id}?Authorization=${key}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data.records.location);
return data.records.location;
};
// get city information
const getCity = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
const query = `?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data);
// console.log(data.records.location[0]);
return data.records.location;
};
getCity('臺南市').then(data => {
return getWeather(data.key);
}).then(data => {
console.log(data);
}).catch(err => console.log(err));
// forecast.js - 2
const key = 'rdec-key-123-45678-011121314';
// get weather information
const getWeather = async (id) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/';
const query = `${id}?Authorization=${key}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data.records.location);
return data;
};
// get city information
const getCity = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
const query = `?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data);
// console.log(data.records.location[0]);
return data;
};
getCity('臺南市').then(data => {
return getWeather(data.result.resource_id);
}).then(data => {
console.log(data);
}).catch(err => console.log(err));
Updating the Location
// app.js
const cityForm = document.querySelector('form');
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(cityDets.result.resource_id);
return {
cityDets: cityDets,
weather: weather
};
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => console.log(data))
.catch(err => console.log(err));
});
Object Shorthand Notation
// app.js
const cityForm = document.querySelector('form');
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(cityDets.result.resource_id);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => console.log(data))
.catch(err => console.log(err));
});
Updating the UI
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Weather</title>
</head>
<body>
<div class="container my-5 mx-auto">
<h1 class="text-muted text-center my-4">Ninja Weather</h1>
<form class="change-location my-4 text-center text-muted">
<label for="city">Enter a location for weather information</label>
<input type="text" name="city" class="form-control p-4">
</form>
<div class="card shadow-lg rounded d-none">
<img src="https://via.placeholder.com/400x300" class="time card-img-top" alt="">
<div class="icon bg-light mx-auto text-center">
<!-- icon -->
</div>
<div class="text-muted text-uppercase text-center details">
<h5 class="my-3">City name</h5>
<div class="my-3">Weather condition</div>
<div class="display-4 my-4">
<span>temp</span>
<span>°C</span>
</div>
</div>
</div>
</div>
<script src="scripts/forecast.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const updateUI = (data) => {
const cityDets = data.cityDets;
const weather = data.weather;
// update details template
details.innerHTML = `
<h4 class="my-3">今明36小時天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${cityDets.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div>↓</div>
<div class="my-3"><br>${cityDets.records.location[0].weatherElement[0].time[1].parameter.parameterName}</div>
<div>↓</div>
<div class="my-3"><br>${cityDets.records.location[0].weatherElement[0].time[2].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>temp</span><br>
<span>${cityDets.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${cityDets.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
<div>↓</div>
<span>${cityDets.records.location[0].weatherElement[2].time[1].parameter.parameterName} - ${cityDets.records.location[0].weatherElement[4].time[1].parameter.parameterName} °C</span>
<div>↓</div>
<span>${cityDets.records.location[0].weatherElement[2].time[2].parameter.parameterName} - ${cityDets.records.location[0].weatherElement[4].time[2].parameter.parameterName} °C</span>
</div>
`;
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(cityDets.result.resource_id);
console.log(cityDets);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
});
// forecast.js
const key = 'rdec-key-123-45678-011121314';
// get weather information
const getWeather = async (id) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/';
const query = `${id}?Authorization=${key}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data.records.location);
return data;
};
// get city information
const getCity = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
const query = `?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data);
// console.log(data.records.location[0]);
return data;
};
Destructuring (解構賦值)
// app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const updateUI = (data) => {
// console.log(data);
// const cityDets = data.cityDets;
// const weather = data.weather;
// destructure properties
const { cityDets, weather } = data;
// update details template
details.innerHTML = `
<h4 class="my-3">今明36小時天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${weather.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div>↓</div>
<div class="my-3"><br>${weather.records.location[0].weatherElement[0].time[1].parameter.parameterName}</div>
<div>↓</div>
<div class="my-3"><br>${weather.records.location[0].weatherElement[0].time[2].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>temp</span><br>
<span>${weather.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
<div>↓</div>
<span>${weather.records.location[0].weatherElement[2].time[1].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[1].parameter.parameterName} °C</span>
<div>↓</div>
<span>${weather.records.location[0].weatherElement[2].time[2].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[2].parameter.parameterName} °C</span>
</div>
`;
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(city);
console.log(cityDets);
console.log(weather);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
});
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Weather</title>
</head>
<body>
<div class="container my-5 mx-auto">
<h1 class="text-muted text-center my-4">Ninja Weather</h1>
<form class="change-location my-4 text-center text-muted">
<label for="city">Enter a location for weather information</label>
<input type="text" name="city" class="form-control p-4">
</form>
<div class="card shadow-lg rounded d-none">
<img src="https://via.placeholder.com/400x300" class="time card-img-top" alt="">
<div class="icon bg-light mx-auto text-center">
<!-- icon -->
</div>
<div class="text-muted text-uppercase text-center details">
<h5 class="my-3">City name</h5>
<div class="my-3">Weather condition</div>
<div class="display-4 my-4">
<span>temp</span>
<span>°C</span>
</div>
</div>
</div>
</div>
<script src="scripts/forecast.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// forecast.js
const key = 'rdec-key-123-45678-011121314';
const id = 'F-C0032-001';
// get weather information
const getWeather = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/';
const query = `${id}?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data.records.location);
return data;
};
// get city information
const getCity = async (city) => {
const base = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
const query = `?Authorization=${key}&locationName=${city}`;
const response = await fetch(base + query);
const data = await response.json();
// console.log(data);
// console.log(data.records.location[0]);
return data;
};
Weather Icons & images
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Weather</title>
</head>
<body>
<div class="container my-5 mx-auto">
<h1 class="text-muted text-center my-4">臺灣天氣</h1>
<form class="change-location my-4 text-center text-muted">
<label for="city">輸入地點取得天氣資訊</label><br>
<span class="text-muted">需輸入完整縣市名稱(例如:臺北市)</span><br><br>
<input type="text" name="city" class="form-control p-4">
</form>
<div class="card shadow-lg rounded d-none">
<img src="https://via.placeholder.com/400x300" class="time card-img-top" alt="">
<div class="icon bg-light mx-auto text-center">
<!-- icon -->
<img src="" alt="">
</div>
<div class="text-muted text-uppercase text-center details">
<h5 class="my-3">City name</h5>
<div class="my-3">Weather condition</div>
<div class="display-4 my-4">
<span>temp</span>
<span>°C</span>
</div>
</div>
</div>
</div>
<script src="scripts/forecast.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const time = document.querySelector('img.time');
const icon = document.querySelector('.icon img');
const updateUI = (data) => {
// console.log(data);
// const cityDets = data.cityDets;
// const weather = data.weather;
// destructure properties
const { cityDets, weather } = data;
// update details template
details.innerHTML = `
<h4 class="my-3">12小時內天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${weather.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>氣溫</span><br>
<span>${weather.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
</div>
`;
// update the night/day & icon images
const iconSrc = `img/icons/${weather.records.location[0].weatherElement[0].time[0].parameter.parameterValue}.svg`;
icon.setAttribute('src', iconSrc);
let timeSrc = null;
if(weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") >= 0){
timeSrc = 'img/day.svg';
} else {
timeSrc = 'img/night.svg';
}
time.setAttribute('src', timeSrc);
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(city);
console.log(cityDets);
console.log(weather);
// console.log(weather.records.location[0].weatherElement[0].time[0].startTime);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
});
// style.css
body{
background: #eeedec;
letter-spacing: 0.2em;
font-size: 0.8em;
}
.container{
max-width: 400px;
}
.icon{
position: relative;
top: -50px;
border-radius: 50%;
width: 100px;
margin-bottom: -50px;
}
Ternary Operator (三元運算子)
// app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const time = document.querySelector('img.time');
const icon = document.querySelector('.icon img');
const updateUI = (data) => {
// console.log(data);
// const cityDets = data.cityDets;
// const weather = data.weather;
// destructure properties
const { cityDets, weather } = data;
// update details template
details.innerHTML = `
<h4 class="my-3">12小時內天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${weather.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>氣溫</span><br>
<span>${weather.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
</div>
`;
// update the night/day & icon images
const iconSrc = `img/icons/${weather.records.location[0].weatherElement[0].time[0].parameter.parameterValue}.svg`;
icon.setAttribute('src', iconSrc);
let timeSrc = weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") >= 0 ? 'img/day.svg' : 'img/night.svg';
// let timeSrc = null;
// if(weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") >= 0){
// timeSrc = 'img/day.svg';
// } else {
// timeSrc = 'img/night.svg';
// }
time.setAttribute('src', timeSrc);
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(city);
console.log(cityDets);
console.log(weather);
// console.log(weather.records.location[0].weatherElement[0].time[0].startTime);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
});
// ternary operator
// const result = true ? 'value 1' : 'value 2';
// console.log(result);
Local Storage
What is Local Storage?
Application Data
- Set up a database to store & retrieve data
- Use local storage to store and retrieve data
Local Storage
// Google Console
> window
< Window {window: Window, self: Window, document: document, name: '', location: Location, …}
>
Storing & Getting Data
- Google Console
- sandbox.js
- Google Application → Storage → Local Storage
// Google Console - 1
> window.localStorage
< Storage {length: 0}
> localStorage
< Storage {length: 0}
>
// sandbox.js - 2
// store data in local storage
localStorage.setItem('name', 'mario');
localStorage.setItem('age', 50);
// get data from local storage
let name = localStorage.getItem('name');
let age = localStorage.getItem('age');
console.log(name, age);
// updating data
localStorage.setItem('name', 'luigi');
localStorage.age = '40';
name = localStorage.getItem('name');
age = localStorage.getItem('age');
console.log(name, age);
// Google Console - 2
Deleting Storage Data
- sandbox.js
- Google Console
- Google Application
// sandbox.js - 1
// store data in local storage
localStorage.setItem('name', 'mario');
localStorage.setItem('age', 50);
// get data from local storage
let name = localStorage.getItem('name');
let age = localStorage.getItem('age');
console.log(name, age);
// deleting data from local storage
// Google Console - 1
mario 50
> localStorage
< Storage {name: 'mario', age: '50', length: 2}
>
// sandbox.js - 2
// store data in local storage
localStorage.setItem('name', 'mario');
localStorage.setItem('age', 50);
// get data from local storage
let name = localStorage.getItem('name');
let age = localStorage.getItem('age');
console.log(name, age);
// deleting data from local storage
// localStorage.removeItem('name');
localStorage.clear();
name = localStorage.getItem('name');
age = localStorage.getItem('age');
console.log(name, age);
// Google Console - 2
mario 50
null null
>
Stringifying & Parsing Data
- sandbox.js
- Google Console
- Google Application
// sandbox.js
const todos = [
{text: 'play mariokart', author: 'shaun'},
{text: 'buy some milk', author: 'mario'},
{text: 'buy some bread', author: 'luigi'}
];
// console.log(JSON.stringify(todos));
localStorage.setItem('todos', JSON.stringify(todos));
const stored = localStorage.getItem('todos');
console.log(JSON.parse(stored));
// Google Console
(3) [{...}, {...}, {...}]
>
Updating the Weather App
- app.js
- Google Application
// app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const time = document.querySelector('img.time');
const icon = document.querySelector('.icon img');
const updateUI = (data) => {
// console.log(data);
// const cityDets = data.cityDets;
// const weather = data.weather;
// destructure properties
const { cityDets, weather } = data;
// update details template
details.innerHTML = `
<h4 class="my-3">12小時內天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${weather.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>氣溫</span><br>
<span>${weather.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
</div>
`;
// update the night/day & icon images
const iconSrc = `img/icons/${weather.records.location[0].weatherElement[0].time[0].parameter.parameterValue}.svg`;
icon.setAttribute('src', iconSrc);
let timeSrc = weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") || weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("12:00:00") >= 0 ? 'img/day.svg' : 'img/night.svg';
// let timeSrc = null;
// if(weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") >= 0){
// timeSrc = 'img/day.svg';
// } else {
// timeSrc = 'img/night.svg';
// }
time.setAttribute('src', timeSrc);
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
const updateCity = async (city) => {
// console.log(city);
const cityDets = await getCity(city);
const weather = await getWeather(city);
console.log(cityDets);
console.log(weather);
// console.log(weather.records.location[0].weatherElement[0].time[0].startTime);
return { cityDets, weather };
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
// set local storage
localStorage.setItem('city', city);
});
// ternary operator
// const result = true ? 'value 1' : 'value 2';
// console.log(result);
if(localStorage.getItem('city')){
updateCity(localStorage.getItem('city'))
.then(data => updateUI(data))
.catch(err => console.log(err));
};
Object Oriented JavaScript (物件導向)
What is OOP? (物件導向程式設計)
// Google Console
> const names = ['shaun', 'crystal']
< undefined
> names
< (2) ["shaun", "crystal"]
> const ages = new Array(20,25,30)
< undefined
> ages
< (3) [20, 25, 30]
> const userOne = {}
< undefined
> userOne
< {}
> const userTwo = new Object();
< undefined
> userTwo
< {}
> const name = 'mario'
< undefined
> name
< "mario"
> name.length
< 5
> name.toUpperCase()
< "MARIO"
> const nameTwo = new String('ryu');
< undefined
> nameTwo
< String {"ryu"}
> new Number(5)
< Number {5}
> new Boolean(true)
< Boolean {true}
>
Object Literal Recap (物件實字回顧)
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<h1>Object Oriented JavaScript</h1>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js - 1
const userOne = {
username: 'ryu',
email: 'ryu@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userOne.email, userOne.username);
userOne.login();
const userTwo = {
username: 'chun li',
email: 'chun.li@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userTwo.email, userTwo.username);
userTwo.login();
// Google Console - 1
ryu@thenetninja.co.uk ryu
the user logged in
chun.li@thenetninja.co.uk chun li
the user logged in
>
// sandbox.js - 2
const userOne = {
username: 'ryu',
email: 'ryu@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userOne.email, userOne.username);
userOne.login();
const userTwo = {
username: 'chun li',
email: 'chun.li@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userTwo.email, userTwo.username);
userTwo.login();
const userThree = new User('shaun@thenetninja.co.uk', 'shaun');
Classes (類別)
// sandbox.js
const userOne = {
username: 'ryu',
email: 'ryu@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userOne.email, userOne.username);
userOne.login();
const userTwo = {
username: 'chun li',
email: 'chun.li@thenetninja.co.uk',
login(){
console.log('the user logged in');
},
logout(){
console.log('the user logged out');
}
};
console.log(userTwo.email, userTwo.username);
userTwo.login();
const userThree = new User('shaun@thenetninja.co.uk', 'shaun');
Classes
- A Class is like a blueprint(藍圖) for an object (it describes how one should be made)
Class Constructors (類別建構子)
// sandbox.js - 1
class User {
constructor(){
// set up properties
this.username = 'mario';
}
}
const userOne = new User();
const userTwo = new User();
console.log(userOne, userTwo);
// the 'new' keyword
// 1 - it creates a new empty object {}
// 2 - it binds the value of 'this' to the new empty object
// 3 - it calls the constructor function to 'build' the object
// Google Console - 1
User {username: 'mario'} User {username: 'mario'}
>
// sandbox.js - 2
class User {
constructor(username){
// set up properties
this.username = username;
}
}
const userOne = new User('mario');
const userTwo = new User('luigi');
console.log(userOne, userTwo);
// the 'new' keyword
// 1 - it creates a new empty object {}
// 2 - it binds the value of 'this' to the new empty object
// 3 - it calls the constructor function to 'build' the object
// Google Console - 2
User {username: 'mario'} User {username: 'luigi'}
>
// sandbox.js - 3
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
// the 'new' keyword
// 1 - it creates a new empty object {}
// 2 - it binds the value of 'this' to the new empty object
// 3 - it calls the constructor function to 'build' the object
// Google Console - 3
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
>
Class Methods & Method Chaining
// sandbox.js - 1
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
}
login(){
console.log(`${this.username} just logged in`);
}
logout(){
console.log(`${this.username} just logged out`);
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login();
userTwo.login();
// Google Console - 1
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
mario just logged in
luigi just logged in
>
// sandbox - 2
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
}
login(){
console.log(`${this.username} just logged in`);
}
logout(){
console.log(`${this.username} just logged out`);
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.logout();
userTwo.logout();
// Google Console - 2
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
mario just logged out
luigi just logged out
>
// sandbox.js - 3
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
this.score = 0;
}
login(){
console.log(`${this.username} just logged in`);
return this;
}
logout(){
console.log(`${this.username} just logged out`);
return this;
}
incScore(){
this.score += 1;
console.log(`${this.username} has a score of ${this.score}`);
return this;
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login().incScore().incScore().logout();
// Google Console - 3
User {username: 'mario', email: 'mario@thenetninja.co.uk', score: 0}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', score: 0}
mario just logged in
mario has a score of 1
mario has a score of 2
mario just logged out
>
Class Inheritance (類別繼承) (subclasses) (子類別)
// sandbox.js - 1
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
this.score = 0;
}
login(){
console.log(`${this.username} just logged in`);
return this;
}
logout(){
console.log(`${this.username} just logged out`);
return this;
}
incScore(){
this.score += 1;
console.log(`${this.username} has a score of ${this.score}`);
return this;
}
}
class Admin extends User{
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk');
console.log(userOne, userTwo, userThree);
// Google Console - 1
User {username: 'mario', email: 'mario@thenetninja.co.uk', score: 0}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', score: 0}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0}
>
// Google Console - 2
User {username: 'mario', email: 'mario@thenetninja.co.uk', score: 0}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', score: 0}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0}
> userThree.login();
shaun just logged in
< Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0}
>
// sandbox - 3
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
this.score = 0;
}
login(){
console.log(`${this.username} just logged in`);
return this;
}
logout(){
console.log(`${this.username} just logged out`);
return this;
}
incScore(){
this.score += 1;
console.log(`${this.username} has a score of ${this.score}`);
return this;
}
}
class Admin extends User{
deleteUser(user){
users = users.filter((u) => {
return u.username !== user.username;
});
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk');
console.log(userOne, userTwo, userThree);
let users = [userOne, userTwo, userThree];
console.log(users);
userThree.deleteUser(userTwo);
console.log(users);
// Google Console - 3
User {username: 'mario', email: 'mario@thenetninja.co.uk', score: 0}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', score: 0}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0}
(3) [User, User, Admin]
(2) [User, Admin]
>
// sandbox.js - 3 縮寫
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
this.score = 0;
}
login(){
console.log(`${this.username} just logged in`);
return this;
}
logout(){
console.log(`${this.username} just logged out`);
return this;
}
incScore(){
this.score += 1;
console.log(`${this.username} has a score of ${this.score}`);
return this;
}
}
class Admin extends User{
deleteUser(user){
users = users.filter(u => u.username !== user.username);
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk');
console.log(userOne, userTwo, userThree);
let users = [userOne, userTwo, userThree];
console.log(users);
userThree.deleteUser(userTwo);
console.log(users);
// Google Console - 3 縮寫
User {username: 'mario', email: 'mario@thenetninja.co.uk', score: 0}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', score: 0}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0}
(3) [User, User, Admin]
(2) [User, Admin]
>
Super()
// sandbox.js
class User {
constructor(username, email){
// set up properties
this.username = username;
this.email = email;
this.score = 0;
}
login(){
console.log(`${this.username} just logged in`);
return this;
}
logout(){
console.log(`${this.username} just logged out`);
return this;
}
incScore(){
this.score += 1;
console.log(`${this.username} has a score of ${this.score}`);
return this;
}
}
class Admin extends User{
constructor(username, email, title){
super(username, email);
this.title = title;
}
deleteUser(user){
users = users.filter(u => u.username !== user.username);
}
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk', 'black-belt-ninja');
console.log(userThree);
// Google Console
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', score: 0, title: 'black-belt-ninja'}
>
Constructors (under the hood)
// sandbox.js
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
this.login = function(){
console.log(`${this.username} has logged in`);
};
}
// class User {
// constructor(username, email){
// // set up properties
// this.username = username;
// this.email = email;
// }
// }
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login();
// the 'new' keyword
// 1 - it creates a new empty object {}
// 2 - it binds the value of 'this' to the new empty object
// 3 - it calls the constructor function to 'build' the object
// Google Console
User {username: 'mario', email: 'mario@thenetninja.co.uk', login: ƒ}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk', login: ƒ}
mario has logged in
>
Prototype Model (原型模型)
不懂可以重複觀看、練習。
// sandbox.js - 1
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
this.login = function(){
console.log(`${this.username} has logged in`);
};
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login();
// Google Console - 1
User User
mario has logged in
> const nums = [1,2,3,4,5]
< undefined
> nums
< (5) [1,2,3,4,5]
>
Prototypes (原型)
- Every object in JavaScript has a prototype
- Prototypes contain all the methods for that object type
// Google Console - 2
User User
mario has logged in
> const nums = [1,2,3,4,5]
< undefined
> nums
< (5) [1,2,3,4,5]
> Array.prototype
< [constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, find: ƒ, …]
> Date.prototype
< {constructor: ƒ, toString: ƒ, toDateString: ƒ, toTimeString: ƒ, toISOString: ƒ, …}
> User.prototype
< {constructor: ƒ}
>
// sandbox.js - 3
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
};
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login();
// Google Console - 3
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
mario has logged in
> User.prototype
< {login: f, constructor: f}
>
// sandbox.js - 4
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
};
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login();
userOne.logout();
// Google Console - 4
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
mario has logged in
mario has logged out
>
// sandbox.js - 5
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
return this;
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
return this;
};
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
console.log(userOne, userTwo);
userOne.login().logout();
// Google Console - 5
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
mario has logged in
mario has logged out
>
Prototypal Inheritance
不懂可以重複觀看、練習。
// sandbox.js - 1
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
return this;
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
return this;
};
function Admin(username, email){
User.call(this, username, email);
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk');
console.log(userOne, userTwo, userThree);
userOne.login().logout();
// Google Console - 1
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk'}
mario has logged in
mario has logged out
>
// sandbox.js - 2
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
return this;
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
return this;
};
function Admin(username, email, title){
User.call(this, username, email);
this.title = title;
}
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk', 'black-belt-ninja');
console.log(userOne, userTwo, userThree);
userOne.login().logout();
// Google Console - 2
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', title: 'black-belt-ninja'}
mario has logged in
mario has logged out
>
// Google Console - 3
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', title: 'black-belt-ninja'}
mario has logged in
mario has logged out
> Admin.prototype
< {constructor: f}
>
// sandbox.js - 4
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
return this;
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
return this;
};
function Admin(username, email, title){
User.call(this, username, email);
this.title = title;
}
Admin.prototype = Object.create(User.prototype);
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk', 'black-belt-ninja');
console.log(userOne, userTwo, userThree);
userOne.login().logout();
// Google Console - 4
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', title: 'black-belt-ninja'}
mario has logged in
mario has logged out
> Admin.prototype
< User {}
>
// sandbox.js - 5
// constructor functions
function User(username, email){
this.username = username;
this.email = email;
}
User.prototype.login = function(){
console.log(`${this.username} has logged in`);
return this;
};
User.prototype.logout = function(){
console.log(`${this.username} has logged out`);
return this;
};
function Admin(username, email, title){
User.call(this, username, email);
this.title = title;
}
Admin.prototype = Object.create(User.prototype);
Admin.prototype.deleteUser = function(){
// delete a user
};
const userOne = new User('mario', 'mario@thenetninja.co.uk');
const userTwo = new User('luigi', 'luigi@thenetninja.co.uk');
const userThree = new Admin('shaun', 'shaun@thenetninja.co.uk', 'black-belt-ninja');
console.log(userOne, userTwo, userThree);
userOne.login().logout();
// Google Console - 5
User {username: 'mario', email: 'mario@thenetninja.co.uk'}
User {username: 'luigi', email: 'luigi@thenetninja.co.uk'}
Admin {username: 'shaun', email: 'shaun@thenetninja.co.uk', title: 'black-belt-ninja'}
mario has logged in
mario has logged out
>
Built-in Objects
// Google Console
> new Array(1,2,3,4,5)
< (5) [1,2,3,4,5]
> new Object
< {}
> new XMLHttpRequest
< XMLHttpRequest {onreadystatechange: null, readyState: 0, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}
>
Making a Forecast Class (weather app)
// scripts/forecast.js
class Forecast{
constructor(){
this.key = 'rdec-key-123-45678-011121314';
this.weatherURI = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/';
this.cityURI = 'https://opendata.cwb.gov.tw/api/v1/rest/datastore/F-C0032-001';
this.id = 'F-C0032-001';
}
async updateCity(city){
const cityDets = await this.getCity(city);
const weather = await this.getWeather(city);
return { cityDets, weather };
}
async getCity(city){
const query = `?Authorization=${this.key}&locationName=${city}`;
const response = await fetch(this.cityURI + query);
const data = await response.json();
return data;
}
async getWeather(city){
const query = `${this.id}?Authorization=${this.key}&locationName=${city}`;
const response = await fetch(this.weatherURI + query);
const data = await response.json();
return data;
}
}
// scripts/app.js
const cityForm = document.querySelector('form');
const card = document.querySelector('.card');
const details = document.querySelector('.details');
const time = document.querySelector('img.time');
const icon = document.querySelector('.icon img');
const forecast = new Forecast();
// console.log(forecast);
const updateUI = (data) => {
// destructure properties
const { cityDets, weather } = data;
// update details template
details.innerHTML = `
<h4 class="my-3">12小時內天氣預報</h4>
<h5 class="my-3">${cityDets.records.location[0].locationName}</h5>
<div class="my-3">${weather.records.location[0].weatherElement[0].time[0].parameter.parameterName}</div>
<div class="display-4 my-4">
<span>氣溫</span><br>
<span>${weather.records.location[0].weatherElement[2].time[0].parameter.parameterName} - ${weather.records.location[0].weatherElement[4].time[0].parameter.parameterName} °C</span>
</div>
`;
// update the night/day & icon images
const iconSrc = `img/icons/${weather.records.location[0].weatherElement[0].time[0].parameter.parameterValue}.svg`;
icon.setAttribute('src', iconSrc);
const timeSrc = weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("06:00:00") || weather.records.location[0].weatherElement[0].time[0].startTime.indexOf("12:00:00") >= 0 ? 'img/day.svg' : 'img/night.svg';
time.setAttribute('src', timeSrc);
// remove the d-none class if present
if(card.classList.contains('d-none')){
card.classList.remove('d-none');
}
};
cityForm.addEventListener('submit', e => {
// prevent default action
e.preventDefault();
// get city value
const city = cityForm.city.value.trim();
cityForm.reset();
// update the ui with new city
forecast.updateCity(city)
.then(data => updateUI(data))
.catch(err => console.log(err));
// set local storage
localStorage.setItem('city', city);
});
if(localStorage.getItem('city')){
forecast.updateCity(localStorage.getItem('city'))
.then(data => updateUI(data))
.catch(err => console.log(err));
};
Databases (Firebase)
NoSQL Databases
Storing Data
- Websites work with data
- blog posts, todos, comments, user info, scores etc
- We can store this data in database
- Firebase (by Google)
Storing Data
NoSQL Databases
Firebase & Firestore
操作步驟
- GO TO CONSOLE
- 新的 Firebase 專案 > 新增專案
- 專案名稱 udemy-modern-javascript
- 適用於 Firebase 專案的 Google Analytics (分析)
- 這裡我先不使用
- 建立專案
- 建構 > Firestore Database
- 建立資料庫
- Cloud Firestore 的安全規則
- 以測試模式啟動 (在這裡先使用這個)
以正式版模式啟動 (在未來使用上安全是重要的)
- 以測試模式啟動 (在這裡先使用這個)
- 設定 Cloud Firestore 位置
- 選擇東亞啟用
- 新增集合 (Add collection)
- 集合 ID (Collection ID) – recipes
- 新增第一份文件 (Add first document)
- 欄位(Field) title、類型(Type) string、值(Value) veg & tofu ninja curry
- 欄位(Field) author、類型(Type) string、值(Value) ryu
- 欄位(Field) created_at 日期(Date) 2022年1月24日、類型(Type) timestamp
- 自動產生的 ID、然後儲存
- 新增文件 (Add a document)
- 欄位(Field) title、類型(Type) string、值(Value) spring green burrito
- 欄位(Field) author、類型(Type) string、值(Value) chun li
- 欄位(Field) created_at 日期(Date) 2022年1月25日、類型(Type) timestamp
- 自動產生的 ID、然後儲存
可以參考 The Net Ninja Youtube Channel 的其他課程。
課程補充文件
// In index.html file put the script tag like this:
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-firestore.js"></script>
<script>
// replace "xxxx" with your own generated firebase config
const firebaseConfig = {
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
authDomain: 'xxxxxxxxxxxxxxxxxxxxx.com',
projectId: 'xxxxx-js',
storageBucket: 'xxxxxxxxxxx.com',
messagingSenderId: 'xxxxxxxxxxxxxxxxx',
appId: 'xxxxxxxxxxxxxxxxx',
measurementId: 'xxxxxxxxxxxx'
};
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="sandbox.js"></script>
// in sandbox.js file
db.collection('recipes')
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
console.log(doc.data());
});
})
.catch((err) => {
console.log(err);
});
Connecting to Firestore
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<h1>Databases(Firebase)</h1>
<script src="sandbox.js"></script>
</body>
</html>
操作步驟
- 建立一個 index.html 檔案
- 專案總覽 udemy-modern-javascript
- 首先請新增應用程式 > 選擇網頁
- 將 Firebase 新增至您的網頁應用程式
- 註冊應用程式 >應用程式暱稱 udemy-modern-javascript > 註冊應用程式
- 新增 Firebase SDK > 使用 <script> 標記 (選擇這個)
- 這個教學影片版本是使用 5.9.1
- 載入 Bootstrap v4.6
- 製作 index.html 的 HTML Template
注意:
Make sure you use the same version of Firebase as me – 5.9.1 – to make sure everything works the same way as in the videos.
There will be a chapter at the end of the course showing how to use the most recent version of Firebase as well (version 9)
// index.html - HTML Template
<div class="container my-5">
<h2>Recipes</h2>
<ul>
<li>Mushroom pie</li>
<li>Veg curry</li>
</ul>
<form>
<label for="recipe">Add a new recipe:</label>
<div class="input-group">
<input type="text" class="form-control" id="recipe" required>
<div class="input-group-append">
<input type="submit" value="add" class="btn btn-outline-secondary">
</div>
</div>
</form>
</div>
// index.html - 串接資料查閱 Firebase
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<div class="container my-5">
<h2>Recipes</h2>
<ul>
<li>Mushroom pie</li>
<li>Veg curry</li>
</ul>
<form>
<label for="recipe">Add a new recipe:</label>
<div class="input-group">
<input type="text" class="form-control" id="recipe" required>
<div class="input-group-append">
<input type="submit" value="add" class="btn btn-outline-secondary">
</div>
</div>
</form>
</div>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="sandbox.js"></script>
</body>
</html>
Getting Collections
// sandbox.js
// index.html - 串接資料查閱 firebase
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<div class="container my-5">
<h2>Recipes</h2>
<ul>
</ul>
<form>
<label for="recipe">Add a new recipe:</label>
<div class="input-group">
<input type="text" class="form-control" id="recipe" required>
<div class="input-group-append">
<input type="submit" value="add" class="btn btn-outline-secondary">
</div>
</div>
</form>
</div>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
const list = document.querySelector('ul');
const addRecipe = (recipe) => {
// console.log(recipe.created_at.toDate());
let time = recipe.created_at.toDate();
let html = `
<li>
<div>${recipe.title}</div>
<div>${time}</div>
</li>
`;
// console.log(html);
list.innerHTML += html;
}
// get documents
db.collection('recipes').get().then(snapshot => {
// when we have the data
snapshot.docs.forEach(doc => {
// console.log(doc.data());
addRecipe(doc.data());
});
}).catch(err => {
console.log(err);
});
Saving Documents
// sandbox.js
const list = document.querySelector('ul');
const form = document.querySelector('form');
const addRecipe = (recipe) => {
// console.log(recipe.created_at.toDate());
let time = recipe.created_at.toDate();
let html = `
<li>
<div>${recipe.title}</div>
<div>${time}</div>
</li>
`;
// console.log(html);
list.innerHTML += html;
}
// get documents
db.collection('recipes').get().then(snapshot => {
// when we have the data
snapshot.docs.forEach(doc => {
// console.log(doc.data());
addRecipe(doc.data());
});
}).catch(err => {
console.log(err);
});
// add documents
form.addEventListener('submit', e => {
e.preventDefault();
const now = new Date();
const recipe = {
title: form.recipe.value,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
db.collection('recipes').add(recipe).then(() => {
console.log('recipe added');
}).catch(err => {
console.log(err);
});
});
Deleting Documents
不懂可重複觀看、練習。
// index.html - 串接資料查閱 Firebase
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<div class="container my-5">
<h2>Recipes</h2>
<ul>
</ul>
<form>
<label for="recipe">Add a new recipe:</label>
<div class="input-group">
<input type="text" class="form-control" id="recipe" required>
<div class="input-group-append">
<input type="submit" value="add" class="btn btn-outline-secondary">
</div>
</div>
</form>
</div>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
const list = document.querySelector('ul');
const form = document.querySelector('form');
const addRecipe = (recipe, id) => {
// console.log(recipe.created_at.toDate());
let time = recipe.created_at.toDate();
let html = `
<li data-id="${id}">
<div>${recipe.title}</div>
<div>${time}</div>
<button class="btn btn-danger btn-sm my-2">delete</button>
</li>
`;
// console.log(html);
list.innerHTML += html;
}
// get documents
db.collection('recipes').get().then(snapshot => {
// when we have the data
snapshot.docs.forEach(doc => {
// console.log(doc.data());
// console.log(doc.id);
addRecipe(doc.data(), doc.id);
});
}).catch(err => {
console.log(err);
});
// add documents
form.addEventListener('submit', e => {
e.preventDefault();
const now = new Date();
const recipe = {
title: form.recipe.value,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
db.collection('recipes').add(recipe).then(() => {
console.log('recipe added');
}).catch(err => {
console.log(err);
});
});
// deleting data
list.addEventListener('click', e => {
// console.log(e);
if(e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.getAttribute('data-id');
// console.log(id);
db.collection('recipes').doc(id).delete().then(() => {
console.log('recipe deleted');
});
}
});
Real-time Listeners
不懂可重複觀看、練習。
// sandbox.js
const list = document.querySelector('ul');
const form = document.querySelector('form');
const addRecipe = (recipe, id) => {
// console.log(recipe.created_at.toDate());
let time = recipe.created_at.toDate();
let html = `
<li data-id="${id}">
<div>${recipe.title}</div>
<div>${time}</div>
<button class="btn btn-danger btn-sm my-2">delete</button>
</li>
`;
// console.log(html);
list.innerHTML += html;
}
const deleteRecipe = (id) => {
const recipes = document.querySelectorAll('li');
recipes.forEach(recipe => {
if(recipe.getAttribute('data-id') === id){
recipe.remove();
}
});
}
// get documents
db.collection('recipes').onSnapshot(snapshot => {
// console.log(snapshot.docChanges());
snapshot.docChanges().forEach(change => {
// console.log(change);
const doc = change.doc;
// console.log(doc);
if(change.type === 'added'){
addRecipe(doc.data(), doc.id);
} else if (change.type === 'removed'){
deleteRecipe(doc.id);
}
})
});
// add documents
form.addEventListener('submit', e => {
e.preventDefault();
const now = new Date();
const recipe = {
title: form.recipe.value,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
db.collection('recipes').add(recipe).then(() => {
console.log('recipe added');
}).catch(err => {
console.log(err);
});
});
// deleting data
list.addEventListener('click', e => {
// console.log(e);
if(e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.getAttribute('data-id');
// console.log(id);
db.collection('recipes').doc(id).delete().then(() => {
console.log('recipe deleted');
});
}
});
Unsubscribing
// index.html - 串接資料查閱 Firebase
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<div class="container my-5">
<h2>Recipes</h2>
<ul>
</ul>
<form>
<label for="recipe">Add a new recipe:</label>
<div class="input-group">
<input type="text" class="form-control" id="recipe" required>
<div class="input-group-append">
<input type="submit" value="add" class="btn btn-outline-secondary">
</div>
</div>
</form>
<button>unsubscribe from changes</button>
</div>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
const list = document.querySelector('ul');
const form = document.querySelector('form');
const button = document.querySelector('button');
const addRecipe = (recipe, id) => {
// console.log(recipe.created_at.toDate());
let time = recipe.created_at.toDate();
let html = `
<li data-id="${id}">
<div>${recipe.title}</div>
<div>${time}</div>
<button class="btn btn-danger btn-sm my-2">delete</button>
</li>
`;
// console.log(html);
list.innerHTML += html;
}
const deleteRecipe = (id) => {
const recipes = document.querySelectorAll('li');
recipes.forEach(recipe => {
if(recipe.getAttribute('data-id') === id){
recipe.remove();
}
});
}
// get documents
const unsub = db.collection('recipes').onSnapshot(snapshot => {
// console.log(snapshot.docChanges());
snapshot.docChanges().forEach(change => {
// console.log(change);
const doc = change.doc;
// console.log(doc);
if(change.type === 'added'){
addRecipe(doc.data(), doc.id);
} else if (change.type === 'removed'){
deleteRecipe(doc.id);
}
})
});
// add documents
form.addEventListener('submit', e => {
e.preventDefault();
const now = new Date();
const recipe = {
title: form.recipe.value,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
db.collection('recipes').add(recipe).then(() => {
console.log('recipe added');
}).catch(err => {
console.log(err);
});
});
// deleting data
list.addEventListener('click', e => {
// console.log(e);
if(e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.getAttribute('data-id');
// console.log(id);
db.collection('recipes').doc(id).delete().then(() => {
console.log('recipe deleted');
});
}
});
// unsub from database changes
button.addEventListener('click', () => {
unsub();
console.log('unsubscribed from collection changes');
});
Project – Real-time Chatroom
非常有趣,不懂可以重複觀看、練習。
Project Preview & Setup
操作步驟
- 建立一個專案資料夾 ninja_chat
- 建立 index.html 檔案
- 建立一個 scripts 資料夾
- 在 scripts 資料夾裡面建立以下檔案
- app.js
- ui.js
- chat.js
- 載入 JS 檔案,chat.js、ui.js、app.js
- 載入 CSS 檔案,bootstrap v4.6 cdn
- 建立 style.css 檔案
- 載入 CSS 檔案,style.css
- 開啟 Live Server
HTML Template
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Chat</title>
</head>
<body>
<!-- container & title -->
<div class="container my-4">
<h1 class="my-4 text-center">Ninja Chat</h1>
<!-- buttons for chatrooms -->
<div class="chat-rooms mb-3 text-center">
<div class="my-2">Choose a chatroom:</div>
<button class="btn" id="general">#general</button>
<button class="btn" id="gaming">#gaming</button>
<button class="btn" id="music">#music</button>
<button class="btn" id="ninjas">#ninjas</button>
</div>
<!-- chat list / window -->
<div class="chat-window">
<ul class="chat-list list-group"></ul>
</div>
<!-- new chat form -->
<form class="new-chat my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Your message:</div>
</div>
<input type="text" id="message" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="send">
</div>
</div>
</form>
<!-- update name form -->
<form class="new-name my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Update name:</div>
</div>
<input type="text" id="name" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="update">
</div>
</div>
<div class="update-mssg"></div>
</form>
</div>
<script src="scripts/chat.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// style.css
.container{
max-width: 600px;
}
.btn{
background: #43d9be;
color: white;
outline: none !important;
box-shadow: none !important;
}
.btn:focus{
outline: none !important;
}
Connecting to Firebase
Make sure you use the same version of Firebase as me – 5.9.1 – to make sure everything works the same way as in the videos.
There will be a chapter at the end of the course showing how to use the most recent version of Firebase as well (version 9)
操作步驟
- 載入 SDK 設定和配置 (使用 udermy-modern-javascript 來練習)
- 點選 udemy-modern-javascript、選擇 Firestore Database
- 並不是每次都需要手動新增集合(collection),這邊是想要新增兩個虛假的文件
- 開始新增集合 (Set collection ID)
- 集合ID (Collection ID) chats
- 新增第一份文件、自動產生 ID,然後儲存
- 欄位(Field) message、類型(Types) string、值(Value) hey guys
- 欄位(Field) username、類型(Types) string、值(Value) yoshi
- 欄位(Field) room、類型(Types) string、值(Value) general
- 欄位(Field) created_at、類型(Types) timestamp 2022年1月27日
- 新增文件、自動產生 ID、然後儲存
- 欄位(Field) message、類型(Types) string、值(Value) Yo Yoshi
- 欄位(Field) username、類型(Types) string、值(Value) mario
- 欄位(Field) room、類型(Types) string、值(Value) general
- 欄位(Field) created_at、類型 timestamp 2022年1月27日
// index.html 串接資料查閱 Firebase
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Chat</title>
</head>
<body>
<!-- container & title -->
<div class="container my-4">
<h1 class="my-4 text-center">Ninja Chat</h1>
<!-- buttons for chatrooms -->
<div class="chat-rooms mb-3 text-center">
<div class="my-2">Choose a chatroom:</div>
<button class="btn" id="general">#general</button>
<button class="btn" id="gaming">#gaming</button>
<button class="btn" id="music">#music</button>
<button class="btn" id="ninjas">#ninjas</button>
</div>
<!-- chat list / window -->
<div class="chat-window">
<ul class="chat-list list-group"></ul>
</div>
<!-- new chat form -->
<form class="new-chat my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Your message:</div>
</div>
<input type="text" id="message" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="send">
</div>
</div>
</form>
<!-- update name form -->
<form class="new-name my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Update name:</div>
</div>
<input type="text" id="name" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="update">
</div>
</div>
<div class="update-mssg"></div>
</form>
</div>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="scripts/chat.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
Chatroom Class & Adding Chats
chat.js 從零開始撰寫程式碼,
不懂可重複觀看、練習。
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
}
const chatroom = new Chatroom('gaming', 'shaun');
// console.log(chatroom);
chatroom.addChat('hello everyone')
.then(() => console.log('chat added'))
.catch(err => console.log(err));
Setting up a Real-time Listener
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
getChats(callback){
this.chats
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
// update the ui
callback(change.doc.data());
}
});
});
}
}
const chatroom = new Chatroom('gaming', 'shaun');
chatroom.getChats((data) => {
console.log(data);
});
測試 – 在 Firestore Database 新增文件是否會 console.log 出物件資料
- 新增文件
- 欄位(Field) message、類型(Types) string、值(Value) test
Complex Queries
不懂可重複觀看、練習。
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
getChats(callback){
this.chats
.where('room', '==', this.room)
.orderBy('created_at')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
// update the ui
callback(change.doc.data());
}
});
});
}
}
const chatroom = new Chatroom('general', 'shaun');
chatroom.getChats((data) => {
console.log(data);
});
操作步驟
- 在 Firestore Database 移除 test 文件
- 在 orderBy(‘created_at) 產生錯誤時 Google Console 有顯示如何建立 index、開啟後按建立
Updating the Room & Username
不懂可重複觀看、練習。
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
this.unsub;
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
getChats(callback){
this.unsub = this.chats
.where('room', '==', this.room)
.orderBy('created_at')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
// update the ui
callback(change.doc.data());
}
});
});
}
updateName(username){
this.username = username;
}
updateRoom(room){
this.room = room;
console.log('room updated');
if(this.undub){
this.unsub();
}
}
}
const chatroom = new Chatroom('general', 'shaun');
chatroom.getChats((data) => {
console.log(data);
});
setTimeout(() => {
chatroom.updateRoom('gaming');
chatroom.updateName('yoshi');
chatroom.getChats((data) => {
console.log(data);
});
chatroom.addChat('hello');
}, 3000);
Creating a ChatUI Class
不懂可重複觀看、練習。
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
this.unsub;
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
getChats(callback){
this.unsub = this.chats
.where('room', '==', this.room)
.orderBy('created_at')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
// update the ui
callback(change.doc.data());
}
});
});
}
updateName(username){
this.username = username;
}
updateRoom(room){
this.room = room;
console.log('room updated');
if(this.undub){
this.unsub();
}
}
}
// app.js
// dom queries
const chatList = document.querySelector('.chat-list');
// class instances
const chatUI = new ChatUI(chatList);
const chatroom = new Chatroom('general', 'shaun');
// get chats and render
chatroom.getChats(data => chatUI.render(data));
// ui.js
// render chat templates to the DOM
// clear the list of chats (when the room changes)
class ChatUI {
constructor(list){
this.list = list;
}
render(data){
const html = `
<li class="list-group-item">
<span class="username">${data.username}</span>
<span class="message">${data.message}</span>
<div class="time">${data.created_at.toDate()}</div>
</li>
`;
this.list.innerHTML += html;
}
}
Formatting the Dates
GitHub Course
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Ninja Chat</title>
</head>
<body>
<!-- container & title -->
<div class="container my-4">
<h1 class="my-4 text-center">Ninja Chat</h1>
<!-- buttons for chatrooms -->
<div class="chat-rooms mb-3 text-center">
<div class="my-2">Choose a chatroom:</div>
<button class="btn" id="general">#general</button>
<button class="btn" id="gaming">#gaming</button>
<button class="btn" id="music">#music</button>
<button class="btn" id="ninjas">#ninjas</button>
</div>
<!-- chat list / window -->
<div class="chat-window">
<ul class="chat-list list-group"></ul>
</div>
<!-- new chat form -->
<form class="new-chat my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Your message:</div>
</div>
<input type="text" id="message" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="send">
</div>
</div>
</form>
<!-- update name form -->
<form class="new-name my-3">
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text">Update name:</div>
</div>
<input type="text" id="name" class="form-control" required>
<div class="input-group-append">
<input type="submit" class="btn" value="update">
</div>
</div>
<div class="update-mssg"></div>
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.9.0/date_fns.min.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.9.1/firebase-firestore.js"></script>
<script>
// Initialize Firebase
const firebaseConfig = {
apiKey: "",
authDomain: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
</script>
<script src="scripts/chat.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
// ui.js
// render chat templates to the DOM
// clear the list of chats (when the room changes)
class ChatUI {
constructor(list){
this.list = list;
}
render(data){
const when = dateFns.distanceInWordsToNow(
data.created_at.toDate(),
{ addSuffix: true }
);
const html = `
<li class="list-group-item">
<span class="username">${data.username}</span>
<span class="message">${data.message}</span>
<div class="time">${when}</div>
</li>
`;
this.list.innerHTML += html;
}
}
// style.css
.container{
max-width: 600px;
}
.btn{
background: #43d9be;
color: white;
outline: none !important;
box-shadow: none !important;
}
.btn:focus{
outline: none !important;
}
.username{
font-weight: bold;
}
.time{
font-size: 0.7em;
color: #999;
}
Sending New Chats
不懂可重複觀看、練習。
// app.js
// dom queries
const chatList = document.querySelector('.chat-list');
const newChatForm = document.querySelector('.new-chat');
// add a new chat
newChatForm.addEventListener('submit', e => {
e.preventDefault();
const message = newChatForm.message.value.trim();
chatroom.addChat(message)
.then(() => newChatForm.reset())
.catch(err => console.log(err));
});
// class instances
const chatUI = new ChatUI(chatList);
const chatroom = new Chatroom('general', 'shaun');
// get chats and render
chatroom.getChats(data => chatUI.render(data));
Changing Username & Local Storage
重要,不懂可重複觀看、練習。
// app.js - 1
// dom queries
const chatList = document.querySelector('.chat-list');
const newChatForm = document.querySelector('.new-chat');
const newNameForm = document.querySelector('.new-name');
const updateMssg = document.querySelector('.update-mssg');
// add a new chat
newChatForm.addEventListener('submit', e => {
e.preventDefault();
const message = newChatForm.message.value.trim();
chatroom.addChat(message)
.then(() => newChatForm.reset())
.catch(err => console.log(err));
});
// update username
newNameForm.addEventListener('submit', e => {
e.preventDefault();
// update name via chatroom
const newName = newNameForm.name.value.trim();
chatroom.updateName(newName);
// reset the form
newNameForm.reset();
// show then hide the update message
updateMssg.innerText = `Your name was updated to ${newName}`;
setTimeout(() => updateMssg.innerText = '', 3000);
});
// class instances
const chatUI = new ChatUI(chatList);
const chatroom = new Chatroom('general', 'shaun');
// get chats and render
chatroom.getChats(data => chatUI.render(data));
// style.css
.container{
max-width: 600px;
}
.btn{
background: #43d9be;
color: white;
outline: none !important;
box-shadow: none !important;
}
.btn:focus{
outline: none !important;
}
.username{
font-weight: bold;
}
.time{
font-size: 0.7em;
color: #999;
}
.update-mssg{
text-align: center;
margin: 20px auto;
}
// Google Console
> chatroom
< Chatroom {room: 'general', username: 'luigi', chats: t, unsub: ƒ}
// chat.js
// adding new chat documents
// setting up a real-time listener to get new chats
// updating the username
// updating the room
class Chatroom {
constructor(room, username){
this.room = room;
this.username = username;
this.chats = db.collection('chats');
this.unsub;
}
async addChat(message){
// format a chat object
const now = new Date();
const chat = {
message,
username: this.username,
room: this.room,
created_at: firebase.firestore.Timestamp.fromDate(now)
};
// save the chat document
const response = await this.chats.add(chat);
return response;
}
getChats(callback){
this.unsub = this.chats
.where('room', '==', this.room)
.orderBy('created_at')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
// update the ui
callback(change.doc.data());
}
});
});
}
updateName(username){
this.username = username;
localStorage.setItem('username', username);
}
updateRoom(room){
this.room = room;
console.log('room updated');
if(this.undub){
this.unsub();
}
}
}
// app.js - 2
// dom queries
const chatList = document.querySelector('.chat-list');
const newChatForm = document.querySelector('.new-chat');
const newNameForm = document.querySelector('.new-name');
const updateMssg = document.querySelector('.update-mssg');
// add a new chat
newChatForm.addEventListener('submit', e => {
e.preventDefault();
const message = newChatForm.message.value.trim();
chatroom.addChat(message)
.then(() => newChatForm.reset())
.catch(err => console.log(err));
});
// update username
newNameForm.addEventListener('submit', e => {
e.preventDefault();
// update name via chatroom
const newName = newNameForm.name.value.trim();
chatroom.updateName(newName);
// reset the form
newNameForm.reset();
// show then hide the update message
updateMssg.innerText = `Your name was updated to ${newName}`;
setTimeout(() => updateMssg.innerText = '', 3000);
});
// check local storage for a name
const username = localStorage.username ? localStorage.username : 'anon';
// class instances
const chatUI = new ChatUI(chatList);
const chatroom = new Chatroom('general', username);
// get chats and render
chatroom.getChats(data => chatUI.render(data));
// Google Console - test
> chatroom
< Chatroom {room: 'general', username: 'anon', chats: t, unsub: ƒ}
>
// Goolge Console - Update name: luigi
> chatroom
< Chatroom {room: 'general', username: 'anon', chats: t, unsub: ƒ}
> chatroom
< Chatroom {room: 'general', username: 'luigi', chats: t, unsub: ƒ}
>
Updating the Room
不懂可重複觀看、練習。
// app.js
// dom queries
const chatList = document.querySelector('.chat-list');
const newChatForm = document.querySelector('.new-chat');
const newNameForm = document.querySelector('.new-name');
const updateMssg = document.querySelector('.update-mssg');
const rooms = document.querySelector('.chat-rooms');
// add a new chat
newChatForm.addEventListener('submit', e => {
e.preventDefault();
const message = newChatForm.message.value.trim();
chatroom.addChat(message)
.then(() => newChatForm.reset())
.catch(err => console.log(err));
});
// update username
newNameForm.addEventListener('submit', e => {
e.preventDefault();
// update name via chatroom
const newName = newNameForm.name.value.trim();
chatroom.updateName(newName);
// reset the form
newNameForm.reset();
// show then hide the update message
updateMssg.innerText = `Your name was updated to ${newName}`;
setTimeout(() => updateMssg.innerText = '', 3000);
});
// update the chat room
rooms.addEventListener('click', e => {
// console.log(e);
if(e.target.tagName === 'BUTTON'){
chatUI.clear();
chatroom.updateRoom(e.target.getAttribute('id'));
chatroom.getChats(chat => chatUI.render(chat));
}
});
// check local storage for a name
const username = localStorage.username ? localStorage.username : 'anon';
// class instances
const chatUI = new ChatUI(chatList);
const chatroom = new Chatroom('general', username);
// get chats and render
chatroom.getChats(data => chatUI.render(data));
// ui.js
// render chat templates to the DOM
// clear the list of chats (when the room changes)
class ChatUI {
constructor(list){
this.list = list;
}
clear(){
this.list.innerHTML = '';
}
render(data){
const when = dateFns.distanceInWordsToNow(
data.created_at.toDate(),
{ addSuffix: true }
);
const html = `
<li class="list-group-item">
<span class="username">${data.username}</span>
<span class="message">${data.message}</span>
<div class="time">${when}</div>
</li>
`;
this.list.innerHTML += html;
}
}
Testing the App
資源
More ES6 Features
Spread & Rest
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
// rest parameter - 其餘參數
const double = (...nums) => {
console.log(nums);
return nums.map(num => num*2);
}
const result = double(1,3,5,7,9,2,4,6,8);
console.log(result);
// spread syntax (arrays) - 展開語法 (陣列)
const people = ['shaun', 'ryu', 'crystal'];
console.log(...people);
const members = ['mario', 'chun-li', ...people];
console.log(members);
// spread syntax (objects) - 展開語法 (物件)
const person = { name: 'shaun', age: 30, job: 'net ninja'};
const personClone = {...person, location: 'manchester'};
console.log(personClone);
Sets
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
// sets
const namesArray = ['ryu', 'chun-li', 'ryu', 'shaun'];
console.log(namesArray);
// const namesSet = new Set(['ryu', 'chun-li', 'ryu', 'shaun']);
// const namesSet = new Set(namesArray);
// console.log(namesSet);
// const uniqueNames = [...namesSet];
const uniqueNames = [...new Set(namesArray)];
console.log(uniqueNames);
const ages = new Set();
ages.add(20);
ages.add(25).add(30);
ages.add(25);
ages.delete(25);
console.log(ages, ages.size);
console.log(ages.has(30), ages.has(25));
ages.clear();
console.log(ages);
const ninjas = new Set([
{name: 'shaun', age: 30},
{name: 'crystal', age: 29},
{name: 'chun-li', age: 32}
]);
ninjas.forEach(ninja => {
console.log(ninja.name, ninja.age);
});
Symbols (符號)
// index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css" integrity="sha384-zCbKRCUGaJDkqS1kPbPd7TveP5iyJE0EjAuZQTgFLD2ylzuqKfdKlfG/eSrtxUkn" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<title>Modern JavaScript</title>
</head>
<body>
<script src="sandbox.js"></script>
</body>
</html>
// sandbox.js
const symbolOne = Symbol('a generic name');
const symbolTwo = Symbol('a generic name');
console.log(symbolOne, symbolTwo, typeof symbolOne);
console.log(symbolOne === symbolTwo);
const ninja = {};
ninja.age = 30;
ninja['belt'] = 'orange';
ninja['belt'] = 'black';
ninja[symbolOne] = 'ryu';
ninja[symbolTwo] = 'shaun';
console.log(ninja);