資料的儲存以及傳輸,一直以來都是網路技術所發展的一個課題,如何方便且高效率的傳輸,並在其中涵蓋安全與可操作性等,各種需求使得我們應接不暇,而在過去的時間裡,XML 也是其中的一名優秀的佼佼者,但很可惜的是他過於複雜的資料儲存方式,使得使用者還是傾向了更加簡單的 JSON,並且因為 JSON 嚴格來說是從 JS 所衍生過來的,因此優秀的相容性也使得他成為了網站開發者的最愛。
XML - https://www.w3schools.com/xml/
JSON - https://www.json.org/json-en.html
JavaScript Object Notation(JSON)是一種資料交換格式。JSON 的語法非常接近 JavaScript,但嚴格上來說 JSON 並不是 JavaScript 的一個子集(因為說法不一所以存在模糊地帶,但嚴格來說只能算是資料儲存格式)。許多程式語言都支援 JSON,不過 JSON 在基於 JavaScript 的應用程式(包含網站和瀏覽器擴充功能)中特別方便使用。
JSON 可以表示數字、布林值、字串、null、陣列(一些有順序的數值),以及由這些數值所組成的物件(字串和數值的對應表)。
JSON的基本資料類型:
{
'type': 'book',
'pages': 220,
'title': 'My Book',
'author': {
'name': 'Alex',
'age': '33',
'email': 'test@foo.com',
'directions': 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
}
}
以下語法的操作將會把輸入值轉換為 JSON 的格式
JSON.stringify(content);
var book = {
type: 'book',
pages: 220,
title: 'My Book',
author: {
name: 'Alex',
age: '33',
email: 'test@foo.com',
directions: 'Lorem ipsum dolor sit amet consectetur adipisicing elit.',
},
};
console.log(JSON.stringify(book));
// 輸出:{"type":"book","pages":220,"title":"My Book","author":{"name":"Alex","age":"33","email":"test@foo.com","directions":"Lorem ipsum dolor sit amet consectetur adipisicing elit."}}
JSON.parse(content);
var jsonData = {"type":"book","pages":220,"title":"My Book","author":{"name":"Alex","age":"33","email":"test@foo.com","directions":"Lorem ipsum dolor sit amet consectetur adipisicing elit."}}
console.log(JSON.parse(jsonData));
// 輸出:{type: 'book', pages: 220, title: 'My Book', author: {…}}
localStorage 允許使用這在同一個網址底下,進行資料的儲存與放置,並且在同一個網址這一前提下,資料將不會在短時間內被移除,需要注意的是,localStorage 使用的是 JSON 的資料結構,因此我們在操作上需要先將資料傳喚成 JSON,才可以完成上傳的操作
localStorage.setItem('myCat', 'Tom');
let cat = localStorage.getItem('myCat');
localStorage.removeItem('myCat');
localStorage.clear();
project
│ index.html
│
└───assets
│ │
│ └───javascript
│ │ │ script.js
│ │
│ └───style
│ │ style.css
<!DOCTYPE html>
<html lang="zh">
<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" />
<title>Document</title>
<!-- 引入 Material Icon 庫 -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
/>
<link rel="stylesheet" href="assets/style/style.css" />
</head>
<body>
<article class="todo-list">
<form class="todo-list__form">
<input type="text" placeholder="輸入代辦內容" />
<button><span class="material-symbols-outlined"> add </span></button>
</form>
<ol class="todo-list__tabs">
<li class="active">全部</li>
<li>代辦</li>
<li>已完成</li>
</ol>
<ul class="todo-list__items"></ul>
</article>
<script src="assets/javascript/script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 40px;
min-height: 100vh;
background-color: aliceblue;
}
.todo-list {
width: 400px;
}
.todo-list__form {
display: flex;
}
.todo-list__form input {
flex: 1;
padding: 15px;
outline: none;
border: none;
font-size: 1rem;
}
.todo-list__form button {
padding: 15px;
border: none;
background-color: lightslategray;
color: white;
}
.todo-list__tabs {
display: flex;
margin: 10px 0;
background-color: white;
list-style-type: none;
}
.todo-list__tabs > li {
padding: 15px 20px;
letter-spacing: 2px;
cursor: pointer;
transition: background-color 300ms, color 300ms;
}
.todo-list__tabs > li.active,
.todo-list__tabs > li:hover {
background-color: lightslategray;
color: white;
}
.todo-list__items {
display: flex;
flex-direction: column;
list-style-type: none;
}
.todo-list__items li {
display: inline-flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
padding: 10px 20px;
background-color: white;
}
.todo-list__not-found {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
background-color: lightslategray;
color: white;
}
// 去取得 localStorage 上方的資料,若沒有結果的話會回傳結果 null
// 原本使用兩個陣列是指空值合併運算符,但這個操作其實比較新,因此我們可以改用三元運算式進行操作
const todoListData = JSON.parse(localStorage.getItem('todoList'))
? JSON.parse(localStorage.getItem('todoList'))
: [];
// 去取得 DOM 上面 class 名稱是 todo-list 的節點
const todoList = document.querySelector('.todo-list');
// 去取得 todoList 中 class 名稱是 todo-list__form 的節點
const todoListFrom = todoList.querySelector('.todo-list__form');
// 去取得 todoListFrom 中的第一個 button 節點
const todoListFromButton = todoListFrom.querySelector('button');
// 去取得 todoList 中 class 名稱是 todo-list__tabs 的節點
const todoListTabs = todoList.querySelector('.todo-list__tabs');
// 去取得 todoList 中 class 名稱是 todo-list__items 的節點
const todoListItems = todoList.querySelector('.todo-list__items');
// 建立一個協助我們宣染頁面 todo list 的函式
const renderTodoList = () => {
// 在此處先宣告一個將要被宣染到 html 的變數,沒有跟賦值一起操作是為了讓過程比較好修改與理解
let todoListUI;
// 讓要被宣染到 html 的物件,指定成一個 map 出來的新陣列,此處將會回傳一個 html 模板
todoListUI = todoListData.map(
(element) =>
`<li>
<h2>${element.title}</h2>
<span>${element.status}</span>
</li>`,
);
// 這裡可以個別將 todoListUI 與 todoListData 列印出來,並觀察其中的差異
console.log(todoListData); // 陣列資料
console.log(todoListUI); // 要被宣染到 html 的內容
// 這裡會將 todoListItems 這個 node 節點所在的 html,置換為下方的其中一種方式
// 這裡藉由三元運算式的方式,去判斷目前陣列是否有內容(用長度判斷),若沒有內容的話新增一個提示區塊
todoListItems.innerHTML = todoListUI.length
? todoListUI.join('')
: '<div class="todo-list__not-found">目前沒有內容</div>';
};
// 幫 todoListFromButton 這個節點新增按鈕點擊事件
todoListFromButton.addEventListener('click', function (event) {
// 因為我們的 button 是放在 form 中,因此點擊時會直接觸發表單送出操作,這裡是將這一預設操作給停止
event.preventDefault();
// 新增一筆資料到我們的 todoListData 這個 array 上面
todoListData.push({ title: 'ssss', status: '代辦' });
// 將已經新增資料的陣列,轉成 JSON 格式之後上傳到 localStorage 上面,這裡的機碼(儲存位子)使用 todoList
localStorage.setItem('todoList', JSON.stringify(todoListData));
// 觸發畫面更新的函式,因為此時他所倚賴的陣列已經更新了內容,因此會在畫面上增加一筆項目
renderTodoList();
});
// 此處算是初始化的操作,沒有執行的話畫面預設會是空的內容
renderTodoList();
project
│ index.html
│
└───assets
│ │
│ └───javascript
│ │ │ script.js
│ │
│ └───style
│ │ style.css
<!DOCTYPE html>
<html lang="zh">
<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" />
<title>Document</title>
<!-- 引入 Material Icon 庫 -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
/>
<link rel="stylesheet" href="assets/style/style.css" />
</head>
<body>
<article class="todo-list">
<form id="search-todo-list" class="todo-list__form">
<input type="text" id="search" placeholder="請輸入搜尋關鍵字" />
<button><span class="material-symbols-outlined"> search </span></button>
</form>
<form id="create-todo-list" class="todo-list__form">
<input type="text" placeholder="輸入代辦內容" />
<button><span class="material-symbols-outlined"> add </span></button>
</form>
<ol class="todo-list__tabs">
<li class="active">全部</li>
<li>代辦</li>
<li>已完成</li>
</ol>
<ul class="todo-list__items"></ul>
<p class="todo-list__info"></p>
</article>
<script src="assets/javascript/script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 40px;
min-height: 100vh;
background-color: aliceblue;
}
.todo-list {
width: 400px;
}
.todo-list__form {
display: flex;
}
.todo-list__form input {
flex: 1;
padding: 15px;
outline: none;
border: none;
font-size: 1rem;
}
.todo-list__form button {
padding: 15px;
border: none;
background-color: lightslategray;
color: white;
}
.todo-list__tabs {
display: flex;
margin: 10px 0;
background-color: white;
list-style-type: none;
}
.todo-list__tabs > li {
padding: 15px 20px;
letter-spacing: 2px;
cursor: pointer;
transition: background-color 300ms, color 300ms;
}
.todo-list__tabs > li.active,
.todo-list__tabs > li:hover {
background-color: lightslategray;
color: white;
}
.todo-list__items {
display: flex;
flex-direction: column;
list-style-type: none;
}
.todo-list__items li {
display: inline-flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
padding: 10px 20px;
background-color: white;
}
.todo-list__items > li > label {
display: inline-flex;
align-items: center;
flex: 1;
}
.todo-list__items > li > label > input {
margin-right: 10px;
}
.todo-list__not-found {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
background-color: lightslategray;
color: white;
}
// 去取得 DOM 上面 class 名稱是 todo-list 的節點
const todoList = document.querySelector('.todo-list');
// 去取得 todoList 中 class 名稱是 todo-list__form 的節點
const todoListFrom = todoList.querySelector('#create-todo-list');
// 去取得 todoListFrom 中的第一個 button 節點
const todoListFromButton = todoListFrom.querySelector('button');
// 去取得 todoListFrom 中的第一個 input 節點
const todoListFromInput = todoListFrom.querySelector('input');
// 去取得 todoList 中 id 名稱是 search-todo-list 的節點
const todoListSearch = todoList.querySelector('#search-todo-list');
// 去取得 todoListSearch 中的第一個 button 節點
const todoListSearchButton = todoListSearch.querySelector('button');
// 去取得 todoListSearch 中的第一個 input 節點
const todoListSearchInput = todoListSearch.querySelector('input');
// 去取得 todoList 中 class 名稱是 todo-list__tabs 底下的 li 節點
const todoListTabs = todoList.querySelectorAll('.todo-list__tabs li');
// 去取得 todoList 中 class 名稱是 todo-list__items 的節點
const todoListItems = todoList.querySelector('.todo-list__items');
// 去取得 todoList 中 class 名稱是 todo-list__info 的節點
const todoListInfo = todoList.querySelector('.todo-list__info');
// 去取得 localStorage 上方的資料,若沒有結果的話會回傳結果 null
// 使用短路求值的技巧做操作
let todoListData = JSON.parse(localStorage.getItem('todoList')) || [];
// 定義整個 todo list 的初始狀態
let currentStatus = '全部';
// 定義一個關鍵字
let keyword = '';
// 建立一個協助我們宣染頁面 todo list 的函式
const renderTodoList = () => {
// 在此處先宣告一個將要被宣染到 html 的變數,沒有跟賦值一起操作是為了讓過程比較好修改與理解
let todoListUI;
todoListUI =
currentStatus === '全部'
? todoListData
: todoListData.filter((element) => element.status === currentStatus);
todoListUI = todoListUI.filter((element) => element.title.includes(keyword));
// 讓要被宣染到 html 的物件,指定成一個 map 出來的新陣列,此處將會回傳一個 html 模板
todoListUI = todoListUI.map(
(element, index) =>
`<li>
<label for="check_${index}">
<input type="checkbox"
onclick="editTodoItemStatus(${element.id})"
${element.status !== '代辦' ? 'checked' : ''}
id="check_${index}">
<h2>${element.title}</h2>
</label>
<span>${element.status}</span>
<span class="material-symbols-outlined"
onclick="deleteTodoItem(${element.id})">
delete
</span>
</li>`,
);
// 這裡可以個別將 todoListUI 與 todoListData 列印出來,並觀察其中的差異
console.log(todoListData); // 陣列資料
console.log(todoListUI.join('')); // 要被宣染到 html 的內容
// 這裡會將 todoListItems 這個 node 節點所在的 html,置換為下方的其中一種方式
// 這裡藉由三元運算`式的方式,去判斷目前陣列是否有內容(用長度判斷),若沒有內容的話新增一個提示區塊
todoListItems.innerHTML = todoListUI.length
? todoListUI.join('')
: '<div class="todo-list__not-found">目前沒有內容</div>';
todoListInfo.innerHTML = `${
keyword ? '關鍵字:' + keyword : ''
}目前的${currentStatus}共: ${todoListUI.length} 筆`;
};
const updateLocalStorage = () => {
// 將已經新增資料的陣列,轉成 JSON 格式之後上傳到 localStorage 上面,這裡的機碼(儲存位子)使用 todoList
localStorage.setItem('todoList', JSON.stringify(todoListData));
};
const editTodoItemStatus = (id) => {
// 對原始資料的陣列進行搜尋,並取得符合輸入 id 的資料結果
const itemData = todoListData.find((element) => element.id === id);
// 對資料的狀態進行狀態上的判斷,隨後再將判斷出的結果進行賦值操作
itemData.status = itemData.status !== '代辦' ? '代辦' : '已完成';
// 觸發畫面繪製的函式
renderTodoList();
// 觸發 updateLocalStorage 的函式操作
updateLocalStorage();
};
const deleteTodoItem = (id) => {
// 將 todoListData 重新給予一個沒有包括傳入 id 物件的陣列
todoListData = todoListData.filter((element) => element.id !== id);
// 觸發畫面繪製的函式
renderTodoList();
// 觸發 updateLocalStorage 的函式操作
updateLocalStorage();
};
// 幫 todoListFromButton 這個節點新增按鈕點擊事件
todoListFromButton.addEventListener('click', function (event) {
// 因為我們的 button 是放在 form 中,因此點擊時會直接觸發表單送出操作,這裡是將這一預設操作給停止
event.preventDefault();
// 從 todoListFormInput 上方取得他的數值
const inputValue = todoListFromInput.value;
// 判斷 inputValue 在被剪裁去空格之後,是否還有內容值,若變為空字串則會被轉換為 false
if (!inputValue.trim()) {
// 將 todoListFromInput 的數值改為空字串
todoListFromInput.value = '';
// 將函示進行返回操作,當一個函式 return 時後面的程式將不會運作
return;
}
// 將 todoListFromInput 的數值改為空字串
todoListFromInput.value = '';
// 新增一筆資料到我們的 todoListData 這個 array 上面
todoListData.push({
// 幫資料使用目前時間建立一個 id
id: new Date().getTime(),
title: inputValue,
status: '代辦',
});
// 觸發 updateLocalStorage 的函式操作
updateLocalStorage();
// 觸發畫面更新的函式,因為此時他所倚賴的陣列已經更新了內容,因此會在畫面上增加一筆項目
renderTodoList();
});
// 幫 todoListFromButton 這個節點新增按鈕點擊事件
todoListSearchButton.addEventListener('click', function (event) {
// 因為我們的 button 是放在 form 中,因此點擊時會直接觸發表單送出操作,這裡是將這一預設操作給停止
event.preventDefault();
// 設定關鍵字為 input 的 value
keyword = todoListSearchInput.value;
// 觸發畫面更新的函式,因為此時他所倚賴的陣列已經更新了內容,因此會在畫面上增加一筆項目
renderTodoList();
});
// 對 todoListTabs 進行迴圈操作,幫每一個項目都執行一次函式
todoListTabs.forEach((element) => {
// 對目前被執行的項目新增一個點擊事件
element.addEventListener('click', () => {
// 對 todoListTabs 進行迴圈操作,將每一個項目的 class 上的 active 做刪除
todoListTabs.forEach((element) => element.classList.remove('active'));
// 對目前被點擊的項目新增一個 active 的 class
element.classList.add('active');
// 將當前的定義設定為點擊的文字內容
currentStatus = element.innerHTML;
// 觸發畫面更新的函式,
renderTodoList();
});
});
// 此處算是初始化的操作,沒有執行的話畫面預設會是空的內容
renderTodoList();
project
│ index.html
│
└───assets
│ │
│ └───javascript
│ │ │ script.js
│ │
│ └───style
│ │ style.css
<!DOCTYPE html>
<html lang="zh">
<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" />
<title>Document</title>
<!-- 引入 Material Icon 庫 -->
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
/>
<link rel="stylesheet" href="assets/style/style.css" />
</head>
<body>
<article class="todo-list">
<form class="todo-list__form">
<input type="text" placeholder="輸入代辦內容" />
<button><span class="material-symbols-outlined"> add </span></button>
</form>
<ol class="todo-list__tabs">
<li class="active">全部</li>
<li>代辦</li>
<li>已完成</li>
<li>已刪除</li>
</ol>
<ul class="todo-list__items"></ul>
</article>
<script src="assets/javascript/script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 40px;
min-height: 100vh;
background-color: aliceblue;
}
.todo-list {
width: 400px;
}
.todo-list__form {
display: flex;
}
.todo-list__form input {
flex: 1;
padding: 15px;
outline: none;
border: none;
font-size: 1rem;
}
.todo-list__form button {
padding: 15px;
border: none;
background-color: lightslategray;
color: white;
cursor: pointer;
}
.todo-list__tabs {
display: flex;
margin: 10px 0;
background-color: white;
list-style-type: none;
}
.todo-list__tabs > li {
padding: 15px 20px;
letter-spacing: 2px;
cursor: pointer;
transition: background-color 300ms, color 300ms;
}
.todo-list__tabs > li.active,
.todo-list__tabs > li:hover {
background-color: lightslategray;
color: white;
}
.todo-list__items {
display: flex;
flex-direction: column;
list-style-type: none;
perspective: 500pc;
}
.todo-list__items > li {
position: relative;
display: inline-flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
padding: 10px 20px;
background-color: white;
}
.todo-list__items > li:hover > .todo-list__item-actions {
opacity: 1;
transform: rotateY(0deg);
}
.todo-list__item-actions {
position: absolute;
left: -100px;
display: flex;
width: 100px;
height: 100%;
opacity: 0;
transition: transform 400ms, opacity 400ms;
transform: rotateY(90deg);
transform-origin: right;
}
.todo-list__item-actions > li {
display: inline-flex;
align-items: center;
flex: 1;
justify-content: center;
height: 100%;
color: white;
cursor: pointer;
}
.todo-list__item-actions > li:nth-child(1) {
background-color: #f0a48c;
}
.todo-list__item-actions > li:nth-child(2) {
background-color: #70c670;
}
.todo-list__not-found {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
background-color: lightslategray;
color: white;
}
const todoList = document.querySelector('.todo-list');
const todoListFrom = todoList.querySelector('.todo-list__form');
const todoListFromButton = todoListFrom.querySelector('button');
const todoListFromInput = todoListFrom.querySelector('input');
const todoListTabs = todoList.querySelectorAll('.todo-list__tabs li');
const todoListItems = todoList.querySelector('.todo-list__items');
const todoListSourceData = JSON.parse(localStorage.getItem('todoList'))
? JSON.parse(localStorage.getItem('todoList'))
: [];
let currentList = todoListSourceData;
let currentStatus = '全部';
const renderTodoList = () => {
let renderList;
if (currentStatus !== '全部') {
currentList = todoListSourceData.filter(
(element) => element.status === currentStatus,
);
} else {
currentList = todoListSourceData;
}
renderList = currentList.map(
(element) =>
`<li>
<ol class="todo-list__item-actions">
<li onClick="editTodo(${element.id},'已刪除')"><span class="material-symbols-outlined"> delete </span></li>
<li onClick="editTodo(${element.id},'已完成')"><span class="material-symbols-outlined"> check </span></li>
</ol>
<h2>${element.title}</h2>
<span>${element.status}</span>
</li>`,
);
todoListItems.innerHTML = renderList.length
? renderList.join('')
: '<div class="todo-list__not-found">目前沒有內容</div>';
};
const editTodo = (id, status) => {
todoListSourceData.find((e) => e.id === id).status = status;
updateLocalStorage();
renderTodoList();
};
const updateLocalStorage = (newTodo = null) => {
if (newTodo !== null) {
currentList.push(newTodo);
}
localStorage.setItem('todoList', JSON.stringify(todoListSourceData));
};
todoListTabs.forEach((element) => {
element.addEventListener('click', function () {
todoListTabs.forEach((typeItem) => typeItem.classList.remove('active'));
element.classList.add('active');
currentStatus = element.innerHTML;
renderTodoList();
});
});
todoListFromButton.addEventListener('click', function (event) {
event.preventDefault();
const inputValue = todoListFromInput.value;
if (!inputValue.trim()) {
todoListFromInput.value = '';
alert('請輸入空格以外的內容');
return;
}
updateLocalStorage({
id: new Date().getTime(),
title: inputValue,
status: '代辦',
});
renderTodoList();
todoListFromInput.value = '';
});
renderTodoList();
project
│ index.html
│
└───assets
│ │
│ └───javascript
│ │ │ script.js
│ │
│ └───scss
│ │ │ style.scss
│ │
│ └───css
│ │ style.css
│ │ style.css.map
<!DOCTYPE html>
<html lang="zh">
<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" />
<title>購物車練習</title>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0"
/>
<link rel="stylesheet" href="assets/css/style.css" />
</head>
<body>
<main class="main">
<article class="main__container">
<ul class="product-items">
<li>
<header class="product-items__cover">
<img
src="https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg"
/>
</header>
<article class="product-items__info">
<h2>包包</h2>
<b>123</b>
<section class="product-items__number">
<span class="material-symbols-outlined"> remove </span>
<input type="number" value="1" />
<span class="material-symbols-outlined"> add </span>
</section>
<button class="product-items__button">新增商品</button>
</article>
</li>
<li>
<header class="product-items__cover">
<img
src="https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg"
/>
</header>
<article class="product-items__info">
<h2>衣服</h2>
<b>213</b>
<section class="product-items__number">
<span class="material-symbols-outlined"> remove </span>
<input type="number" value="1" />
<span class="material-symbols-outlined"> add </span>
</section>
<button class="product-items__button">新增商品</button>
</article>
</li>
<li>
<header class="product-items__cover">
<img
src="https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg"
/>
</header>
<article class="product-items__info">
<h2>大衣</h2>
<b>321</b>
<section class="product-items__number">
<span class="material-symbols-outlined"> remove </span>
<input type="number" value="1" />
<span class="material-symbols-outlined"> add </span>
</section>
<button class="product-items__button">新增商品</button>
</article>
</li>
</ul>
</article>
<aside class="main__aside">
<header class="main__aside-header"></header>
<ul class="cart-items"></ul>
</aside>
</main>
<script src="assets/javascript/script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
.main {
display: flex;
&__aside {
display: inline-flex;
flex-basis: 280px;
flex-direction: column;
padding: 10px;
}
&__container {
display: inline-flex;
flex: 1;
padding: 10px;
}
&__aside-header {
padding: 20px;
background-color: bisque;
}
}
.product-items {
display: grid;
align-items: flex-start;
width: 100%;
gap: 10px;
grid-template-columns: repeat(3, 1fr);
img {
max-width: 100%;
max-height: 100%;
}
> li {
display: inline-flex;
flex-direction: column;
justify-content: center;
padding: 10px;
border: #e2dfdf solid 1px;
}
&__cover {
display: inline-flex;
align-items: center;
justify-content: center;
height: 200px;
}
&__info {
display: inline-flex;
flex-direction: column;
}
&__number {
display: inline-flex;
margin: 10px 0;
input {
width: 100%;
}
> span {
display: flex;
width: 24px;
height: 24px;
background-color: rgb(82, 82, 144);
color: white;
}
}
&__button {
padding: 10px;
background-color: burlywood;
}
}
.cart-items {
img {
width: 100%;
}
> li {
display: inline-flex;
align-items: center;
margin-top: 10px;
padding: 10px;
border: #e2dfdf solid 1px;
}
&__cover {
flex: 1;
padding: 10px;
}
&__info {
flex: 2;
}
&__number {
display: inline-flex;
input {
width: 100%;
}
> span {
display: flex;
width: 24px;
height: 24px;
background-color: rgb(82, 82, 144);
color: white;
}
}
&__button {
padding: 10px;
width: 100%;
background-color: rgb(161, 53, 53);
color: white;
}
}
const productItems = document.querySelectorAll('.product-items li');
const asideHeader = document.querySelector('.main__aside-header');
const cart = document.querySelector('.cart-items');
let cartItemsData = [];
productItems.forEach((element) => {
const elementButton = element.querySelector('button');
elementButton.addEventListener('click', () => {
const itemName = element.querySelector('h2').innerHTML;
const itemNumber = Number(element.querySelector('input').value);
const cartItem = cartItemsData.find((item) => item.name === itemName);
if (cartItem) {
cartItem.number += itemNumber;
} else {
const newItem = {
name: itemName,
number: itemNumber,
price: element.querySelector('b').innerHTML,
imageSrc: element.querySelector('img').src,
};
cartItemsData.push(newItem);
}
renderCartItems();
});
});
const renderCartItems = () => {
let todoListUI;
todoListUI = cartItemsData.map(
(element) =>
`<li>
<header class="cart-items__cover">
<img
src="${element.imageSrc}"
/>
</header>
<article class="cart-items__info">
<h2>${element.name}</h2>
<b>單價:${element.price}</b>
<section class="cart-items__number">
數量:${element.number}
</section>
<button class="cart-items__button">刪除項目</button>
</article>
</li>`,
);
cart.innerHTML = todoListUI.length
? todoListUI.join('')
: '<li class="todo-list__not-found">目前沒有內容</li>';
renderCartInfo();
createNumberBoxAction();
createItemDeleteAction();
};
const renderCartInfo = () => {
const countNumber = cartItemsData.reduce(
(prev, current) => prev + current.number,
0,
);
const countPrice = cartItemsData.reduce(
(prev, current) => prev + current.number * current.price,
0,
);
asideHeader.innerHTML = `您總計選擇了 ${countNumber} 項商品,共計 NT.${countPrice}元 `;
};
const createNumberBoxAction = () => {
const numberBox = document.querySelectorAll('.product-items__number');
numberBox.forEach((element) => {
let subtractBtn = element.querySelector('span');
let addBtn = element.querySelector('span:nth-child(3)');
let input = element.querySelector('input');
subtractBtn.onclick = () => {
input.value = Number(input.value) - 1;
if (input.value <= 0) {
input.value = 0;
}
};
addBtn.onclick = () => {
input.value = Number(input.value) + 1;
};
input.onkeyup = () => {
if (Number(input.value) < 0) {
input.value = 0;
}
};
});
};
const createItemDeleteAction = () => {
if (!cartItemsData.length) return;
const cartItems = document.querySelectorAll('.cart-items li');
cartItems.forEach((element, index) => {
let deleteButton = element.querySelector('button');
deleteButton.onclick = () => {
cartItemsData.splice(index, 1);
renderCartItems();
};
});
};
renderCartItems();