如何从本地存储阵列中删除唯一项? (到目前为止没有任何效果)
How to delete unique item from local storage array? (Nothing has worked so far)
我完全卡住了。
我正在研究 The Odin Project 的 JavaScript 课程中的 Library project,我正在尝试使用 localStorage 以便用户可以将他们的“图书馆”保存到他们的本地存储.
我以前从未能够 100% 成功地使用 localStorage。这看起来很简单,但由于某种原因,我的大脑中出现了某种关于如何正确使用它的障碍。然而,这一次,我能够让我的程序能够 1) 保存用户数据(通过单击按钮),2) 将其正确加载到页面上的各个“卡片”中,以及 3) 删除整个存储(通过单击另一个按钮)。我对此感到非常兴奋...不知道如何做某事然后让它发挥作用的感觉真好。
让我完全困惑的一件事是能够从 localStorage 数组中删除一个唯一的项目。当你点击我页面上的“新书”按钮时,会出现一个模式,你需要输入书名、作者、页数以及你是否阅读过这本书。还有一个选项可以设置背景颜色。单击“提交”将生成一张包含图书信息的“卡片”。
每张卡片的角落都有一个 X 按钮。单击 X 按钮时,卡片将从页面中删除。我想要它,以便它(意味着卡片的书的对象)也从本地存储阵列中删除(最好是用户不必再次按下“保存到本地存储”按钮,尽管它不是一个交易破坏者)。
每次用户创建一本新书时,该书的详细信息都会作为一个对象保存到一个名为 libraryBooks 的数组中(然后我将其字符串化,以便我可以使用 localStorage)。根据我的理解,为了能够删除数组的各个部分,我需要使用如下代码:
libraryBooks.splice(libraryBooks.indexOf(item), 1);
我想我的问题是我不知道如何获取项目的索引号。我不确定如何使程序遵循“单击此 X 按钮时,在数组中找到该书的对象”的说明(如果有意义的话)。在这一点上,我已经尝试了几种方法,但我完全感到困惑。
是啊!任何帮助都会很棒。我已经检查并评论了我的代码,以试图帮助阐明每件事的作用。我已经包含了全部内容,这样我就不会遗漏与我的问题相关的重要内容,并且它可以在代码片段中运行。但如果没有,这里是代码库和现场演示的链接:
// Link DOM elements
const newBookBtn = document.querySelector('.newbook'); // New Book button
const showBooks = document.querySelector('.show-books'); // div container
const cardClose = document.querySelectorAll('.cardclose'); // button to close card
const openEls = document.querySelectorAll("[data-open]"); // for popup boxes
const closeEls = document.querySelectorAll("[data-close]"); // for popup boxes
const submitBtn = document.querySelector('.submitbtn'); // Submit button (in popup boxes)
const formBoxes = document.querySelectorAll('.form-box'); // Form box within popup box
const formRadio1 = document.querySelector('#bookreadyes'); // Form radio buttons within popup box
const isVisible = "is-visible"; // for popup boxes
const colorDropdown = document.querySelector('select'); // Color-picking dropdown in popup boxes
let libraryBooks = [];
// localstorage buttons (save and delete)
const saveStorage = document.getElementById('save-storage');
const deleteStorage = document.getElementById('delete-storage');
// Button event listeners
saveStorage.addEventListener('click', updateLocalStorage);
deleteStorage.addEventListener('click', deleteLocalStorage);
submitBtn.addEventListener('click', addBookToLibrary);
// If the 'books' key is empty, simply set libraryBooks to empty array.
if (localStorage.getItem('books') === null) {
libraryBooks = [];
// Otherwise, set library books array to get items from the 'books' key
} else {
const booksFromStorage = JSON.parse(localStorage.getItem('books'));
libraryBooks = booksFromStorage;
}
// The Book constructor
function Book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
this.info = function() {
return `${this.title} by ${this.author}, ${this.pages} pages, ${read}`;
}
}
// Add book to library function.
function addBookToLibrary() {
let bookTitle = document.querySelector('#book-title');
let bookAuthor = document.querySelector('#book-author');
let bookPages = document.querySelector('#book-pages');
let bookReadYes = document.querySelector('#bookreadyes')
let bookReadNo = document.querySelector('#bookreadno');
let alertWords = document.querySelector('.alertwords'); // Alert if form elements are empty
let bookRead;
if (bookReadYes.checked) {
bookRead = 'Read';
} else if (bookReadNo.checked) {
bookRead = 'Not read';
}
// Creating a new book object via the Book constructor
let newBook = new Book(bookTitle.value, bookAuthor.value, bookPages.value, bookRead);
// If any form elements are empty, throw error and don't submit book. If none of them are empty, proceed.
if (bookTitle.value.length === 0 || bookAuthor.value.length === 0 || bookPages.value.length === 0) {
alertWords.textContent = 'Please fill in all fields.';
} else {
alertWords.textContent = '';
document.querySelector('.modal.is-visible').classList.remove(isVisible); // Closes the modal
formBoxes.forEach(formBox => {
formBox.value = ""; // Sets the form values so they're blank the next time the New Book button is pressed
});
formRadio1.checked = true; // Set the radio buttons so that the "Yes" button is automatically selected (otherwise, the user's last choice will be selected)
// Push the new book object into libraryBooks array
libraryBooks.push(newBook);
// The rest of the lines of code in this function create the actual book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker());
showBooks.appendChild(newCard);
for (let i = 0; i < libraryBooks.length; i++) {
newCardTitle.innerHTML = `${libraryBooks[i].title}`;
let closeBtn = "<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
newCardTitle.innerHTML += closeBtn;
newCardAuthor.innerHTML = `by ${libraryBooks[i].author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${libraryBooks[i].pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${libraryBooks[i].read}`;
}
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
}
// Stuff for popup capability
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
// Keyboard shortvut for modal: ESC key to close
document.addEventListener("keyup", e => {
// if we press the ESC
if (e.key == "Escape" && document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
cardClose.forEach(card => {
card.addEventListener('click', function() {
this.parentNode.parentNode.removeChild(this.parentNode.parentNode);
return false;
});
})
// Switch function for setting the background color of the book's card
function colorPicker() {
switch (colorDropdown.value) {
case 'red':
return 'cardback-red';
break;
case 'orange':
return 'cardback-orange';
break;
case 'yellow':
return 'cardback-yellow';
break;
case 'green':
return 'cardback-green';
break;
case 'blue':
return 'cardback-blue';
break;
case 'purple':
return 'cardback-purple';
break;
case 'dark':
return 'cardback-dark';
break;
case 'grey':
return 'cardback-grey';
break;
default:
return 'cardback-white';
break;
}
}
// Update local storage
function updateLocalStorage() {
localStorage.setItem('books', JSON.stringify(libraryBooks));
}
// Delete local storage
function deleteLocalStorage() {
window.localStorage.clear();
showBooks.textContent = "";
}
// Get localStorage data and set it to the variable "data"
const data = JSON.parse(localStorage.getItem('books'));
// Load the saved local storage objects into cards (almost identical to addBookToLibrary())
function loadLocalStorage(array, book) {
let bookTitle;
let bookAuthor;
let bookPages;
let bookRead;
for (let i = 0; i < array.length; i++) {
bookTitle = book.title;
bookAuthor = book.author;
bookPages = book.pages;
bookRead = book.read;
}
// Create book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker());
showBooks.appendChild(newCard);
for (let i = 0; i < array.length; i++) {
newCardTitle.innerHTML = `${bookTitle}`;
let closeBtn = "<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
newCardTitle.innerHTML += closeBtn;
newCardAuthor.innerHTML = `by ${bookAuthor}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${bookPages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${bookRead}`;
}
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
// Required in order to load saved books onto page
for (let i = 0; i < data.length; i++) {
loadLocalStorage(data, data[i]);
}
* {
margin: 0;
padding: 0;
}
body,
html {
display: flex;
align-items: center;
flex-direction: column;
background-color: #cedee9;
height: 100vh;
padding: 10px;
font-family: 'Rubik', sans-serif;
}
body {
max-height: 100%;
}
input {
padding: 5px;
}
label {
margin-bottom: 5px;
margin-top: 10px;
font-size: .9rem;
}
input[type='text'] {
font-size: .75rem;
font-family: 'Poppins', sans-serif;
}
.radio-option1 {
margin-right: 5px;
}
h4 {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.2rem;
}
.close {
background: none;
font-size: 1.5rem;
border: 0;
margin-left: 10px;
transition: 0.5s ease;
}
.close:hover {
cursor: pointer;
color: rgb(58, 84, 140);
}
.submitbtn {
border: 1px solid rgb(58, 84, 140);
color: white;
background-color: rgb(58, 84, 140);
padding: 8px 20px;
margin: 0 auto;
width: 100%;
border-radius: 5px;
transition: 0.5s ease;
}
.submitbtn:hover {
cursor: pointer;
background-color: rgb(40, 54, 85);
border-color: rgb(40, 54, 85);
}
.book-form {
display: flex;
flex-direction: column;
width: 200px;
}
/* Testing this comment */
input {
margin-bottom: 10px;
}
p {
margin-top: 5px;
}
.bookcard {
width: 200px;
display: flex;
justify-content: flex-start;
flex-direction: column;
}
.cardbox {
background-color: white;
width: 250px;
margin: 10px;
padding: 12px 20px 20px 20px;
box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;
}
.alertwords {
color: #df0a0a;
}
.show-books {
display: flex;
flex-wrap: wrap;
}
.title-style {
margin-bottom: -5px;
}
.author-style {
font-size: .9rem;
margin-bottom: 20px;
}
.pages-style,
.read-style {
font-size: .9rem;
}
option {
font-family: 'Poppins', sans-serif;
font-size: .8rem;
}
select {
padding: 5px;
font-family: 'Poppins', sans-serif;
font-size: .8rem;
}
#option-red,
.cardback-red {
background-color: rgb(241, 191, 191);
}
#option-orange,
.cardback-orange {
background-color: #ffcb9a;
}
#option-yellow,
.cardback-yellow {
background-color: #fffda1;
}
#option-green,
.cardback-green {
background-color: #9cd6af;
}
#option-blue,
.cardback-blue {
background-color: #a1d3f0;
}
#option-purple,
.cardback-purple {
background-color: #e6c1ff;
}
#option-grey,
.cardback-grey {
background-color: #cfcfcf;
}
#option-dark,
.cardback-dark {
background-color: #282f52;
color: white;
}
/* RESET RULES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
* {
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
.close-modal {
cursor: pointer;
background: transparent;
border: none;
outline: none;
font-size: inherit;
}
.btn-group {
text-align: center;
}
.open-modal {
font-weight: bold;
background: steelblue;
color: white;
padding: 0.75rem 1.75rem;
margin-bottom: 1rem;
border-radius: 5px;
border: 0;
transition: 0.5s ease;
}
.open-modal:hover {
background-color: rgb(40, 54, 85);
cursor: pointer;
}
.open-modal:active {
background-color: rgb(40, 54, 85);
}
.open-modal:focus {
background-color: rgb(40, 54, 85);
}
/* MODAL
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgba(0, 0, 0, 0.781);
cursor: pointer;
visibility: hidden;
opacity: 0;
transition: all 0.35s ease-in;
}
.modal.is-visible {
visibility: visible;
opacity: 1;
}
.modal-dialog {
position: relative;
max-width: 800px;
max-height: 80vh;
border-radius: 5px;
background: white;
overflow: auto;
cursor: default;
}
.modal-dialog>* {
padding: 1rem;
}
.modal-header,
.modal-footer {
font-weight: 700;
background: #a8c0f2;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 1.3rem;
}
.modal-header .close-modal {
font-size: 1.5rem;
}
.modal p+p {
margin-top: 1rem;
}
/* ANIMATIONS
–––––––––––––––––––––––––––––––––––––––––––––––––– */
[data-animation] .modal-dialog {
opacity: 0;
transition: all 0.5s var(--bounceEasing);
}
[data-animation].is-visible .modal-dialog {
opacity: 1;
transition-delay: 0.2s;
}
[data-animation="slideInOutDown"] .modal-dialog {
transform: translateY(100%);
}
[data-animation="slideInOutTop"] .modal-dialog {
transform: translateY(-100%);
}
[data-animation="slideInOutLeft"] .modal-dialog {
transform: translateX(-100%);
}
[data-animation="slideInOutRight"] .modal-dialog {
transform: translateX(100%);
}
[data-animation="zoomInOut"] .modal-dialog {
transform: scale(0.2);
}
[data-animation="rotateInOutDown"] .modal-dialog {
transform-origin: top left;
transform: rotate(-1turn);
}
[data-animation="mixInAnimations"].is-visible .modal-dialog {
animation: mixInAnimations 2s 0.2s linear forwards;
}
[data-animation="slideInOutDown"].is-visible .modal-dialog,
[data-animation="slideInOutTop"].is-visible .modal-dialog,
[data-animation="slideInOutLeft"].is-visible .modal-dialog,
[data-animation="slideInOutRight"].is-visible .modal-dialog,
[data-animation="zoomInOut"].is-visible .modal-dialog,
[data-animation="rotateInOutDown"].is-visible .modal-dialog {
transform: none;
}
@keyframes mixInAnimations {
0% {
transform: translateX(-100%);
}
10% {
transform: translateX(0);
}
20% {
transform: rotate(20deg);
}
30% {
transform: rotate(-20deg);
}
40% {
transform: rotate(15deg);
}
50% {
transform: rotate(-15deg);
}
60% {
transform: rotate(10deg);
}
70% {
transform: rotate(-10deg);
}
80% {
transform: rotate(5deg);
}
90% {
transform: rotate(-5deg);
}
100% {
transform: rotate(0deg);
}
}
/* FOOTER
–––––––––––––––––––––––––––––––––––––––––––––––––– */
footer {
display: flex;
align-items: center;
justify-content: center;
}
.page-footer {
position: fixed;
bottom: 0;
}
.page-footer span {
color: #e31b23;
}
<!DOCTYPE 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">
<title>Library</title>
<script src="https://kit.fontawesome.com/2b4114baf6.js" crossorigin="anonymous"></script>
<!-- Google fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Slab:wght@100;200;300;400;500;600;700;800;900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Source+Sans+Pro:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700;1,900&display=swap"
rel="stylesheet">
<!-- CSS Stylesheet -->
<link href="styles.css" rel="stylesheet">
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<h1>Library</h1>
<div class="btn-group">
<button type="button" class="open-modal" data-open="modal1">
New Book
</button>
</div>
<div class="btn-group">
<button class="open-modal" id="save-storage">
Save local storage
</button>
<button class="open-modal" id="delete-storage">
Delete local storage
</button>
</div>
<!-- Library books container -->
<div class="show-books">
</div>
<!-- Modal code (popup box for new book form) -->
<div class="modal" id="modal1" data-animation="slideInOutLeft">
<div class="modal-dialog">
<header class="modal-header">
Book Details
<button class="close-modal" aria-label="close modal" data-close>
✕
</button>
</header>
<section class="modal-content">
<form class="bookcard">
<label>Title:</label>
<input type="text" class="form-box" id="book-title" placeholder="Fight Club">
<label>Author:</label>
<input type="text" class="form-box" id="book-author" placeholder="Chuck Palahniuk">
<label># of Pages:</label>
<input type="number" class="form-box" id="book-pages" placeholder="208" min="1" max="14000">
<label>Have you read this book?:</label>
<div class="radiobox">
<label class="radio-option1"><input type="radio" name="read" value="yes" id="bookreadyes" class='form-radio' checked> Yes</label>
<label class="radio-option2"><input type="radio" name="read" value="no" id="bookreadno" class='form-radio'> No</label>
</div>
<label>Select card color (optional):</label>
<select class="colorpicker">
<option value='default' selected disabled>Please select one</option>
<option value="white">White (Default)</option>
<option value="blue" id="option-blue">Blue</option>
<option value="purple" id="option-purple">Purple</option>
<option value="green" id="option-green">Green</option>
<option value="grey" id="option-grey">Grey</option>
<option value="red" id="option-red">Red</option>
<option value="orange" id="option-orange">Orange</option>
<option value="yellow" id="option-yellow">Yellow</option>
<option value="dark" id="option-dark">Dark mode</option>
</select>
<p class="alertwords"></p>
</form>
</section>
<footer class="modal-footer">
<button class="submitbtn">Submit</button>
</footer>
</div>
</div>
<footer class="page-footer">
<small>Made with <span>❤</span> by <a href="http://georgemartsoukos.com/" target="_blank">Sara Dunlop</a>
</small>
</footer>
<!-- JS script -->
<script src="script2.js"></script>
</body>
</html>
我查看了您的演示,我认为您可以做的是,在保存书籍对象时,您可以传入一个名为 id 的 属性 以从集合中准确识别书籍。
因此,当您单击 x 按钮时,您将获得该书的详细信息,该书现在有一个 ID,您可以将其过滤掉。
内容如下-
const filteredBooks = existingBooks.filter((el) => el.id !== book.id);
localstorage.setItem('books', filteredBooks);
希望对你有用。
这是适合您的简单修复方法
let closeBtn = `<button type='button' class='close-default' onclick='libraryBooks.splice(libraryBooks.findIndex((book) => book.title === "${bookTitle}" && book.author === "${bookAuthor}"), 1);'>x</button>`;
i
正在从您的数组列表中获取
以防万一你不明白什么是模板文字,我在这里分享 link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
我还为您的 deleteLocalStorage
添加了一个额外的修复程序。您还需要清理 libraryBooks
数组。
function deleteLocalStorage() {
libraryBooks = [] //here is the fix to clean up your array
window.localStorage.clear();
showBooks.textContent = "";
}
你可以在这里查看我的修复
你删除特定书卡的问题
libraryBooks.splice(libraryBooks.indexOf(item), 1);
那是不正确的,因为
indexOf() compares searchElement to elements of the Array using strict
equality (the same method used by the ===, or triple-equals,
operator).
libraryBooks.indexOf 总是导致 -1
在书籍构造函数中创建书籍时可以添加索引并在每本书中递增的解决方案
[
{
index: 0
author: "00"
pages: "200"
read: "Read"
title: "00"
}
]
创建新对象时设置索引++可以直接获取对象
当删除
function deleteBook(book){
libraryBooks.splice(book.index , 1)
}
好吧,我对你的脚本做了一些很好的修改并且它有效
主要问题 是您的关闭按钮没有提供能够删除与其相关的书的侦听器,解决了这个问题,并且为了有趣添加了逻辑以自动保存到 localStorage
代码在下面,但还有一个 link 到 working example
// Link DOM elements
const newBookBtn = document.querySelector('.newbook'); // New Book button
const showBooks = document.querySelector('.show-books'); // div container
const cardClose = document.querySelectorAll('.cardclose'); // button to close card
const openEls = document.querySelectorAll("[data-open]"); // for popup boxes
const closeEls = document.querySelectorAll("[data-close]"); // for popup boxes
const submitBtn = document.querySelector('.submitbtn'); // Submit button (in popup boxes)
const formBoxes = document.querySelectorAll('.form-box'); // Form box within popup box
const formRadio1 = document.querySelector('#bookreadyes'); // Form radio buttons within popup box
const isVisible = "is-visible"; // for popup boxes
const colorDropdown = document.querySelector('select'); // Color-picking dropdown in popup boxes
let libraryBooks = [], automaticallyUpdate = true; //change automaticallyUpdate to false to prevent automatic saving(on changes)
// localstorage buttons (save and delete)
const saveStorage = document.getElementById('save-storage');
const deleteStorage = document.getElementById('delete-storage');
// Button event listeners
saveStorage.addEventListener('click', updateLocalStorage);
deleteStorage.addEventListener('click', deleteLocalStorage);
submitBtn.addEventListener('click', addBookToLibrary);
// The Book constructor
function Book({title, author, pages, read, color}) {
this.title = String(title)
this.author = String(author)
this.pages = Number(pages)
this.read = read?"Read":"Not Read"
this.color = String(color)
this.info = function() {
return `${this.title} by ${this.author}, ${this.pages} pages, ${this.read} with color ${this.color}`;
}
}
//returns your close button with a listener that actually removes the book
//"<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
function closeBar(book){
let btn=document.createElement('button')
btn.className='close-default'
btn.innerHTML='x' //wow almost forgot this
btn.addEventListener('click',()=>{
$(btn).parent().parent().remove()
console.log(book,libraryBooks,libraryBooks.indexOf(book))
libraryBooks.splice(libraryBooks.indexOf(book),1)
if(automaticallyUpdate){updateLocalStorage()} //automatic saving
})
return btn //the button is returned to be placed in its arrangement
}
// Add book to library function.
function addBookToLibrary() {
let color = colorDropdown.value;
let bookTitle = document.querySelector('#book-title');
let bookAuthor = document.querySelector('#book-author');
let bookPages = document.querySelector('#book-pages');
let bookRead = document.querySelector('#bookreadyes').checked; //true if checked, false if not checked
let alertWords = document.querySelector('.alertwords'); // Alert if form elements are empty
// Creating a new book object via the Book constructor
let newBook = new Book({title:bookTitle.value, author:bookAuthor.value, pages:bookPages.value, read:bookRead, color});
// If any form elements are empty, throw error and don't submit book. If none of them are empty, proceed.
if (bookTitle.value.length === 0 || bookAuthor.value.length === 0 || bookPages.value.length === 0) {
alertWords.textContent = 'Please fill in all fields.';
} else {
alertWords.textContent = '';
document.querySelector('.modal.is-visible').classList.remove(isVisible); // Closes the modal
formBoxes.forEach(formBox => {
formBox.value = ""; // Sets the form values so they're blank the next time the New Book button is pressed
});
formRadio1.checked = true; // Set the radio buttons so that the "Yes" button is automatically selected (otherwise, the user's last choice will be selected)
// Push the new book object into libraryBooks array
libraryBooks.push(newBook);
// The rest of the lines of code in this function create the actual book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker(newBook.color));
showBooks.appendChild(newCard);
const {title,author,pages,read}=newBook
newCardTitle.innerHTML = `${title}`;
let closeBtn = closeBar(newBook)
newCardTitle.appendChild(closeBtn);
newCardAuthor.innerHTML = `by ${author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${read}`;
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
if(automaticallyUpdate){updateLocalStorage()} //automatic saving
}
}
// Stuff for popup capability
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
// Keyboard shortvut for modal: ESC key to close
document.addEventListener("keyup", e => {
// if we press the ESC
if (e.key == "Escape" && document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
cardClose.forEach(card => {
card.addEventListener('click', function() {
this.parentNode.parentNode.removeChild(this.parentNode.parentNode);
return false;
});
})
// Switch function for setting the background color of the book's card
let colors = {red:1, orange:1, yellow:1, green:1, blue:1, purple:1, dark:1, grey:1}
//the above variable saves a lot of lines in the colorPicker function
function colorPicker(color) {
if(!colors[color]){return 'cardback-white'}
return 'cardback-'+color
}
// Update local storage
function updateLocalStorage() {
localStorage.setItem('books', JSON.stringify(libraryBooks));
}
// Delete local storage
function deleteLocalStorage() {
window.localStorage.clear();
showBooks.textContent = "";
}
// Get localStorage data and set it to the variable "data"
const data = JSON.parse(localStorage.getItem('books'))||[]
// the "||" in case there was nothing stored yet and it prevents an error from reading map from null
.map(book=>new Book( book )) //convert localStorage data to a list of "Book"s
// Load the saved local storage objects into cards (almost identical to addBookToLibrary())
function loadLocalStorage({title,author,pages,read,color}) {
// EDIT: the for loops in this function are seemingly useless
var newBook=arguments[0]
libraryBooks.push(newBook)
// Create book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker(color));
showBooks.appendChild(newCard);
newCardTitle.innerHTML = `${title}`;
const closeBtn = closeBar(newBook);
newCardTitle.appendChild(closeBtn);
newCardAuthor.innerHTML = `by ${author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${read}`;
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
// Required in order to load saved books onto page
for(let i = 0; i < data.length; i++) {
loadLocalStorage(data[i]);
}
我完全卡住了。
我正在研究 The Odin Project 的 JavaScript 课程中的 Library project,我正在尝试使用 localStorage 以便用户可以将他们的“图书馆”保存到他们的本地存储.
我以前从未能够 100% 成功地使用 localStorage。这看起来很简单,但由于某种原因,我的大脑中出现了某种关于如何正确使用它的障碍。然而,这一次,我能够让我的程序能够 1) 保存用户数据(通过单击按钮),2) 将其正确加载到页面上的各个“卡片”中,以及 3) 删除整个存储(通过单击另一个按钮)。我对此感到非常兴奋...不知道如何做某事然后让它发挥作用的感觉真好。
让我完全困惑的一件事是能够从 localStorage 数组中删除一个唯一的项目。当你点击我页面上的“新书”按钮时,会出现一个模式,你需要输入书名、作者、页数以及你是否阅读过这本书。还有一个选项可以设置背景颜色。单击“提交”将生成一张包含图书信息的“卡片”。
每张卡片的角落都有一个 X 按钮。单击 X 按钮时,卡片将从页面中删除。我想要它,以便它(意味着卡片的书的对象)也从本地存储阵列中删除(最好是用户不必再次按下“保存到本地存储”按钮,尽管它不是一个交易破坏者)。
每次用户创建一本新书时,该书的详细信息都会作为一个对象保存到一个名为 libraryBooks 的数组中(然后我将其字符串化,以便我可以使用 localStorage)。根据我的理解,为了能够删除数组的各个部分,我需要使用如下代码:
libraryBooks.splice(libraryBooks.indexOf(item), 1);
我想我的问题是我不知道如何获取项目的索引号。我不确定如何使程序遵循“单击此 X 按钮时,在数组中找到该书的对象”的说明(如果有意义的话)。在这一点上,我已经尝试了几种方法,但我完全感到困惑。
是啊!任何帮助都会很棒。我已经检查并评论了我的代码,以试图帮助阐明每件事的作用。我已经包含了全部内容,这样我就不会遗漏与我的问题相关的重要内容,并且它可以在代码片段中运行。但如果没有,这里是代码库和现场演示的链接:
// Link DOM elements
const newBookBtn = document.querySelector('.newbook'); // New Book button
const showBooks = document.querySelector('.show-books'); // div container
const cardClose = document.querySelectorAll('.cardclose'); // button to close card
const openEls = document.querySelectorAll("[data-open]"); // for popup boxes
const closeEls = document.querySelectorAll("[data-close]"); // for popup boxes
const submitBtn = document.querySelector('.submitbtn'); // Submit button (in popup boxes)
const formBoxes = document.querySelectorAll('.form-box'); // Form box within popup box
const formRadio1 = document.querySelector('#bookreadyes'); // Form radio buttons within popup box
const isVisible = "is-visible"; // for popup boxes
const colorDropdown = document.querySelector('select'); // Color-picking dropdown in popup boxes
let libraryBooks = [];
// localstorage buttons (save and delete)
const saveStorage = document.getElementById('save-storage');
const deleteStorage = document.getElementById('delete-storage');
// Button event listeners
saveStorage.addEventListener('click', updateLocalStorage);
deleteStorage.addEventListener('click', deleteLocalStorage);
submitBtn.addEventListener('click', addBookToLibrary);
// If the 'books' key is empty, simply set libraryBooks to empty array.
if (localStorage.getItem('books') === null) {
libraryBooks = [];
// Otherwise, set library books array to get items from the 'books' key
} else {
const booksFromStorage = JSON.parse(localStorage.getItem('books'));
libraryBooks = booksFromStorage;
}
// The Book constructor
function Book(title, author, pages, read) {
this.title = title
this.author = author
this.pages = pages
this.read = read
this.info = function() {
return `${this.title} by ${this.author}, ${this.pages} pages, ${read}`;
}
}
// Add book to library function.
function addBookToLibrary() {
let bookTitle = document.querySelector('#book-title');
let bookAuthor = document.querySelector('#book-author');
let bookPages = document.querySelector('#book-pages');
let bookReadYes = document.querySelector('#bookreadyes')
let bookReadNo = document.querySelector('#bookreadno');
let alertWords = document.querySelector('.alertwords'); // Alert if form elements are empty
let bookRead;
if (bookReadYes.checked) {
bookRead = 'Read';
} else if (bookReadNo.checked) {
bookRead = 'Not read';
}
// Creating a new book object via the Book constructor
let newBook = new Book(bookTitle.value, bookAuthor.value, bookPages.value, bookRead);
// If any form elements are empty, throw error and don't submit book. If none of them are empty, proceed.
if (bookTitle.value.length === 0 || bookAuthor.value.length === 0 || bookPages.value.length === 0) {
alertWords.textContent = 'Please fill in all fields.';
} else {
alertWords.textContent = '';
document.querySelector('.modal.is-visible').classList.remove(isVisible); // Closes the modal
formBoxes.forEach(formBox => {
formBox.value = ""; // Sets the form values so they're blank the next time the New Book button is pressed
});
formRadio1.checked = true; // Set the radio buttons so that the "Yes" button is automatically selected (otherwise, the user's last choice will be selected)
// Push the new book object into libraryBooks array
libraryBooks.push(newBook);
// The rest of the lines of code in this function create the actual book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker());
showBooks.appendChild(newCard);
for (let i = 0; i < libraryBooks.length; i++) {
newCardTitle.innerHTML = `${libraryBooks[i].title}`;
let closeBtn = "<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
newCardTitle.innerHTML += closeBtn;
newCardAuthor.innerHTML = `by ${libraryBooks[i].author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${libraryBooks[i].pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${libraryBooks[i].read}`;
}
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
}
// Stuff for popup capability
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
// Keyboard shortvut for modal: ESC key to close
document.addEventListener("keyup", e => {
// if we press the ESC
if (e.key == "Escape" && document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
cardClose.forEach(card => {
card.addEventListener('click', function() {
this.parentNode.parentNode.removeChild(this.parentNode.parentNode);
return false;
});
})
// Switch function for setting the background color of the book's card
function colorPicker() {
switch (colorDropdown.value) {
case 'red':
return 'cardback-red';
break;
case 'orange':
return 'cardback-orange';
break;
case 'yellow':
return 'cardback-yellow';
break;
case 'green':
return 'cardback-green';
break;
case 'blue':
return 'cardback-blue';
break;
case 'purple':
return 'cardback-purple';
break;
case 'dark':
return 'cardback-dark';
break;
case 'grey':
return 'cardback-grey';
break;
default:
return 'cardback-white';
break;
}
}
// Update local storage
function updateLocalStorage() {
localStorage.setItem('books', JSON.stringify(libraryBooks));
}
// Delete local storage
function deleteLocalStorage() {
window.localStorage.clear();
showBooks.textContent = "";
}
// Get localStorage data and set it to the variable "data"
const data = JSON.parse(localStorage.getItem('books'));
// Load the saved local storage objects into cards (almost identical to addBookToLibrary())
function loadLocalStorage(array, book) {
let bookTitle;
let bookAuthor;
let bookPages;
let bookRead;
for (let i = 0; i < array.length; i++) {
bookTitle = book.title;
bookAuthor = book.author;
bookPages = book.pages;
bookRead = book.read;
}
// Create book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker());
showBooks.appendChild(newCard);
for (let i = 0; i < array.length; i++) {
newCardTitle.innerHTML = `${bookTitle}`;
let closeBtn = "<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
newCardTitle.innerHTML += closeBtn;
newCardAuthor.innerHTML = `by ${bookAuthor}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${bookPages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${bookRead}`;
}
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
// Required in order to load saved books onto page
for (let i = 0; i < data.length; i++) {
loadLocalStorage(data, data[i]);
}
* {
margin: 0;
padding: 0;
}
body,
html {
display: flex;
align-items: center;
flex-direction: column;
background-color: #cedee9;
height: 100vh;
padding: 10px;
font-family: 'Rubik', sans-serif;
}
body {
max-height: 100%;
}
input {
padding: 5px;
}
label {
margin-bottom: 5px;
margin-top: 10px;
font-size: .9rem;
}
input[type='text'] {
font-size: .75rem;
font-family: 'Poppins', sans-serif;
}
.radio-option1 {
margin-right: 5px;
}
h4 {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.2rem;
}
.close {
background: none;
font-size: 1.5rem;
border: 0;
margin-left: 10px;
transition: 0.5s ease;
}
.close:hover {
cursor: pointer;
color: rgb(58, 84, 140);
}
.submitbtn {
border: 1px solid rgb(58, 84, 140);
color: white;
background-color: rgb(58, 84, 140);
padding: 8px 20px;
margin: 0 auto;
width: 100%;
border-radius: 5px;
transition: 0.5s ease;
}
.submitbtn:hover {
cursor: pointer;
background-color: rgb(40, 54, 85);
border-color: rgb(40, 54, 85);
}
.book-form {
display: flex;
flex-direction: column;
width: 200px;
}
/* Testing this comment */
input {
margin-bottom: 10px;
}
p {
margin-top: 5px;
}
.bookcard {
width: 200px;
display: flex;
justify-content: flex-start;
flex-direction: column;
}
.cardbox {
background-color: white;
width: 250px;
margin: 10px;
padding: 12px 20px 20px 20px;
box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;
}
.alertwords {
color: #df0a0a;
}
.show-books {
display: flex;
flex-wrap: wrap;
}
.title-style {
margin-bottom: -5px;
}
.author-style {
font-size: .9rem;
margin-bottom: 20px;
}
.pages-style,
.read-style {
font-size: .9rem;
}
option {
font-family: 'Poppins', sans-serif;
font-size: .8rem;
}
select {
padding: 5px;
font-family: 'Poppins', sans-serif;
font-size: .8rem;
}
#option-red,
.cardback-red {
background-color: rgb(241, 191, 191);
}
#option-orange,
.cardback-orange {
background-color: #ffcb9a;
}
#option-yellow,
.cardback-yellow {
background-color: #fffda1;
}
#option-green,
.cardback-green {
background-color: #9cd6af;
}
#option-blue,
.cardback-blue {
background-color: #a1d3f0;
}
#option-purple,
.cardback-purple {
background-color: #e6c1ff;
}
#option-grey,
.cardback-grey {
background-color: #cfcfcf;
}
#option-dark,
.cardback-dark {
background-color: #282f52;
color: white;
}
/* RESET RULES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
* {
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
.close-modal {
cursor: pointer;
background: transparent;
border: none;
outline: none;
font-size: inherit;
}
.btn-group {
text-align: center;
}
.open-modal {
font-weight: bold;
background: steelblue;
color: white;
padding: 0.75rem 1.75rem;
margin-bottom: 1rem;
border-radius: 5px;
border: 0;
transition: 0.5s ease;
}
.open-modal:hover {
background-color: rgb(40, 54, 85);
cursor: pointer;
}
.open-modal:active {
background-color: rgb(40, 54, 85);
}
.open-modal:focus {
background-color: rgb(40, 54, 85);
}
/* MODAL
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgba(0, 0, 0, 0.781);
cursor: pointer;
visibility: hidden;
opacity: 0;
transition: all 0.35s ease-in;
}
.modal.is-visible {
visibility: visible;
opacity: 1;
}
.modal-dialog {
position: relative;
max-width: 800px;
max-height: 80vh;
border-radius: 5px;
background: white;
overflow: auto;
cursor: default;
}
.modal-dialog>* {
padding: 1rem;
}
.modal-header,
.modal-footer {
font-weight: 700;
background: #a8c0f2;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 1.3rem;
}
.modal-header .close-modal {
font-size: 1.5rem;
}
.modal p+p {
margin-top: 1rem;
}
/* ANIMATIONS
–––––––––––––––––––––––––––––––––––––––––––––––––– */
[data-animation] .modal-dialog {
opacity: 0;
transition: all 0.5s var(--bounceEasing);
}
[data-animation].is-visible .modal-dialog {
opacity: 1;
transition-delay: 0.2s;
}
[data-animation="slideInOutDown"] .modal-dialog {
transform: translateY(100%);
}
[data-animation="slideInOutTop"] .modal-dialog {
transform: translateY(-100%);
}
[data-animation="slideInOutLeft"] .modal-dialog {
transform: translateX(-100%);
}
[data-animation="slideInOutRight"] .modal-dialog {
transform: translateX(100%);
}
[data-animation="zoomInOut"] .modal-dialog {
transform: scale(0.2);
}
[data-animation="rotateInOutDown"] .modal-dialog {
transform-origin: top left;
transform: rotate(-1turn);
}
[data-animation="mixInAnimations"].is-visible .modal-dialog {
animation: mixInAnimations 2s 0.2s linear forwards;
}
[data-animation="slideInOutDown"].is-visible .modal-dialog,
[data-animation="slideInOutTop"].is-visible .modal-dialog,
[data-animation="slideInOutLeft"].is-visible .modal-dialog,
[data-animation="slideInOutRight"].is-visible .modal-dialog,
[data-animation="zoomInOut"].is-visible .modal-dialog,
[data-animation="rotateInOutDown"].is-visible .modal-dialog {
transform: none;
}
@keyframes mixInAnimations {
0% {
transform: translateX(-100%);
}
10% {
transform: translateX(0);
}
20% {
transform: rotate(20deg);
}
30% {
transform: rotate(-20deg);
}
40% {
transform: rotate(15deg);
}
50% {
transform: rotate(-15deg);
}
60% {
transform: rotate(10deg);
}
70% {
transform: rotate(-10deg);
}
80% {
transform: rotate(5deg);
}
90% {
transform: rotate(-5deg);
}
100% {
transform: rotate(0deg);
}
}
/* FOOTER
–––––––––––––––––––––––––––––––––––––––––––––––––– */
footer {
display: flex;
align-items: center;
justify-content: center;
}
.page-footer {
position: fixed;
bottom: 0;
}
.page-footer span {
color: #e31b23;
}
<!DOCTYPE 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">
<title>Library</title>
<script src="https://kit.fontawesome.com/2b4114baf6.js" crossorigin="anonymous"></script>
<!-- Google fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Slab:wght@100;200;300;400;500;600;700;800;900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Source+Sans+Pro:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700;1,900&display=swap"
rel="stylesheet">
<!-- CSS Stylesheet -->
<link href="styles.css" rel="stylesheet">
<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
<h1>Library</h1>
<div class="btn-group">
<button type="button" class="open-modal" data-open="modal1">
New Book
</button>
</div>
<div class="btn-group">
<button class="open-modal" id="save-storage">
Save local storage
</button>
<button class="open-modal" id="delete-storage">
Delete local storage
</button>
</div>
<!-- Library books container -->
<div class="show-books">
</div>
<!-- Modal code (popup box for new book form) -->
<div class="modal" id="modal1" data-animation="slideInOutLeft">
<div class="modal-dialog">
<header class="modal-header">
Book Details
<button class="close-modal" aria-label="close modal" data-close>
✕
</button>
</header>
<section class="modal-content">
<form class="bookcard">
<label>Title:</label>
<input type="text" class="form-box" id="book-title" placeholder="Fight Club">
<label>Author:</label>
<input type="text" class="form-box" id="book-author" placeholder="Chuck Palahniuk">
<label># of Pages:</label>
<input type="number" class="form-box" id="book-pages" placeholder="208" min="1" max="14000">
<label>Have you read this book?:</label>
<div class="radiobox">
<label class="radio-option1"><input type="radio" name="read" value="yes" id="bookreadyes" class='form-radio' checked> Yes</label>
<label class="radio-option2"><input type="radio" name="read" value="no" id="bookreadno" class='form-radio'> No</label>
</div>
<label>Select card color (optional):</label>
<select class="colorpicker">
<option value='default' selected disabled>Please select one</option>
<option value="white">White (Default)</option>
<option value="blue" id="option-blue">Blue</option>
<option value="purple" id="option-purple">Purple</option>
<option value="green" id="option-green">Green</option>
<option value="grey" id="option-grey">Grey</option>
<option value="red" id="option-red">Red</option>
<option value="orange" id="option-orange">Orange</option>
<option value="yellow" id="option-yellow">Yellow</option>
<option value="dark" id="option-dark">Dark mode</option>
</select>
<p class="alertwords"></p>
</form>
</section>
<footer class="modal-footer">
<button class="submitbtn">Submit</button>
</footer>
</div>
</div>
<footer class="page-footer">
<small>Made with <span>❤</span> by <a href="http://georgemartsoukos.com/" target="_blank">Sara Dunlop</a>
</small>
</footer>
<!-- JS script -->
<script src="script2.js"></script>
</body>
</html>
我查看了您的演示,我认为您可以做的是,在保存书籍对象时,您可以传入一个名为 id 的 属性 以从集合中准确识别书籍。
因此,当您单击 x 按钮时,您将获得该书的详细信息,该书现在有一个 ID,您可以将其过滤掉。
内容如下-
const filteredBooks = existingBooks.filter((el) => el.id !== book.id);
localstorage.setItem('books', filteredBooks);
希望对你有用。
这是适合您的简单修复方法
let closeBtn = `<button type='button' class='close-default' onclick='libraryBooks.splice(libraryBooks.findIndex((book) => book.title === "${bookTitle}" && book.author === "${bookAuthor}"), 1);'>x</button>`;
i
正在从您的数组列表中获取
以防万一你不明白什么是模板文字,我在这里分享 link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
我还为您的 deleteLocalStorage
添加了一个额外的修复程序。您还需要清理 libraryBooks
数组。
function deleteLocalStorage() {
libraryBooks = [] //here is the fix to clean up your array
window.localStorage.clear();
showBooks.textContent = "";
}
你可以在这里查看我的修复
你删除特定书卡的问题
libraryBooks.splice(libraryBooks.indexOf(item), 1);
那是不正确的,因为
indexOf() compares searchElement to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).
libraryBooks.indexOf 总是导致 -1
在书籍构造函数中创建书籍时可以添加索引并在每本书中递增的解决方案
[
{
index: 0
author: "00"
pages: "200"
read: "Read"
title: "00"
}
]
创建新对象时设置索引++可以直接获取对象 当删除
function deleteBook(book){
libraryBooks.splice(book.index , 1)
}
好吧,我对你的脚本做了一些很好的修改并且它有效
主要问题 是您的关闭按钮没有提供能够删除与其相关的书的侦听器,解决了这个问题,并且为了有趣添加了逻辑以自动保存到 localStorage
代码在下面,但还有一个 link 到 working example
// Link DOM elements
const newBookBtn = document.querySelector('.newbook'); // New Book button
const showBooks = document.querySelector('.show-books'); // div container
const cardClose = document.querySelectorAll('.cardclose'); // button to close card
const openEls = document.querySelectorAll("[data-open]"); // for popup boxes
const closeEls = document.querySelectorAll("[data-close]"); // for popup boxes
const submitBtn = document.querySelector('.submitbtn'); // Submit button (in popup boxes)
const formBoxes = document.querySelectorAll('.form-box'); // Form box within popup box
const formRadio1 = document.querySelector('#bookreadyes'); // Form radio buttons within popup box
const isVisible = "is-visible"; // for popup boxes
const colorDropdown = document.querySelector('select'); // Color-picking dropdown in popup boxes
let libraryBooks = [], automaticallyUpdate = true; //change automaticallyUpdate to false to prevent automatic saving(on changes)
// localstorage buttons (save and delete)
const saveStorage = document.getElementById('save-storage');
const deleteStorage = document.getElementById('delete-storage');
// Button event listeners
saveStorage.addEventListener('click', updateLocalStorage);
deleteStorage.addEventListener('click', deleteLocalStorage);
submitBtn.addEventListener('click', addBookToLibrary);
// The Book constructor
function Book({title, author, pages, read, color}) {
this.title = String(title)
this.author = String(author)
this.pages = Number(pages)
this.read = read?"Read":"Not Read"
this.color = String(color)
this.info = function() {
return `${this.title} by ${this.author}, ${this.pages} pages, ${this.read} with color ${this.color}`;
}
}
//returns your close button with a listener that actually removes the book
//"<button type='button' class='close-default' onclick='$(this).parent().parent().remove();'>x</button>";
function closeBar(book){
let btn=document.createElement('button')
btn.className='close-default'
btn.innerHTML='x' //wow almost forgot this
btn.addEventListener('click',()=>{
$(btn).parent().parent().remove()
console.log(book,libraryBooks,libraryBooks.indexOf(book))
libraryBooks.splice(libraryBooks.indexOf(book),1)
if(automaticallyUpdate){updateLocalStorage()} //automatic saving
})
return btn //the button is returned to be placed in its arrangement
}
// Add book to library function.
function addBookToLibrary() {
let color = colorDropdown.value;
let bookTitle = document.querySelector('#book-title');
let bookAuthor = document.querySelector('#book-author');
let bookPages = document.querySelector('#book-pages');
let bookRead = document.querySelector('#bookreadyes').checked; //true if checked, false if not checked
let alertWords = document.querySelector('.alertwords'); // Alert if form elements are empty
// Creating a new book object via the Book constructor
let newBook = new Book({title:bookTitle.value, author:bookAuthor.value, pages:bookPages.value, read:bookRead, color});
// If any form elements are empty, throw error and don't submit book. If none of them are empty, proceed.
if (bookTitle.value.length === 0 || bookAuthor.value.length === 0 || bookPages.value.length === 0) {
alertWords.textContent = 'Please fill in all fields.';
} else {
alertWords.textContent = '';
document.querySelector('.modal.is-visible').classList.remove(isVisible); // Closes the modal
formBoxes.forEach(formBox => {
formBox.value = ""; // Sets the form values so they're blank the next time the New Book button is pressed
});
formRadio1.checked = true; // Set the radio buttons so that the "Yes" button is automatically selected (otherwise, the user's last choice will be selected)
// Push the new book object into libraryBooks array
libraryBooks.push(newBook);
// The rest of the lines of code in this function create the actual book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker(newBook.color));
showBooks.appendChild(newCard);
const {title,author,pages,read}=newBook
newCardTitle.innerHTML = `${title}`;
let closeBtn = closeBar(newBook)
newCardTitle.appendChild(closeBtn);
newCardAuthor.innerHTML = `by ${author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${read}`;
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
if(automaticallyUpdate){updateLocalStorage()} //automatic saving
}
}
// Stuff for popup capability
for (const el of openEls) {
el.addEventListener("click", function() {
const modalId = this.dataset.open;
document.getElementById(modalId).classList.add(isVisible);
});
}
for (const el of closeEls) {
el.addEventListener("click", function() {
this.parentElement.parentElement.parentElement.classList.remove(isVisible);
});
}
document.addEventListener("click", e => {
if (e.target == document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
// Keyboard shortvut for modal: ESC key to close
document.addEventListener("keyup", e => {
// if we press the ESC
if (e.key == "Escape" && document.querySelector(".modal.is-visible")) {
document.querySelector(".modal.is-visible").classList.remove(isVisible);
}
});
cardClose.forEach(card => {
card.addEventListener('click', function() {
this.parentNode.parentNode.removeChild(this.parentNode.parentNode);
return false;
});
})
// Switch function for setting the background color of the book's card
let colors = {red:1, orange:1, yellow:1, green:1, blue:1, purple:1, dark:1, grey:1}
//the above variable saves a lot of lines in the colorPicker function
function colorPicker(color) {
if(!colors[color]){return 'cardback-white'}
return 'cardback-'+color
}
// Update local storage
function updateLocalStorage() {
localStorage.setItem('books', JSON.stringify(libraryBooks));
}
// Delete local storage
function deleteLocalStorage() {
window.localStorage.clear();
showBooks.textContent = "";
}
// Get localStorage data and set it to the variable "data"
const data = JSON.parse(localStorage.getItem('books'))||[]
// the "||" in case there was nothing stored yet and it prevents an error from reading map from null
.map(book=>new Book( book )) //convert localStorage data to a list of "Book"s
// Load the saved local storage objects into cards (almost identical to addBookToLibrary())
function loadLocalStorage({title,author,pages,read,color}) {
// EDIT: the for loops in this function are seemingly useless
var newBook=arguments[0]
libraryBooks.push(newBook)
// Create book card on page
const newCard = document.createElement('div');
const newCardTitle = document.createElement('h4');
const newCardAuthor = document.createElement('p');
const newCardPages = document.createElement('p');
const newCardRead = document.createElement('span');
newCardTitle.setAttribute('class', 'title-style');
newCardAuthor.setAttribute('class', 'author-style');
newCardPages.setAttribute('class', 'pages-style');
newCardRead.setAttribute('class', 'read-style');
newCard.classList.add('isVisible', 'cardbox', colorPicker(color));
showBooks.appendChild(newCard);
newCardTitle.innerHTML = `${title}`;
const closeBtn = closeBar(newBook);
newCardTitle.appendChild(closeBtn);
newCardAuthor.innerHTML = `by ${author}`;
newCardPages.innerHTML = `<strong>Pages</strong>: ${pages}`;
newCardRead.innerHTML = `<strong>Status</strong>: ${read}`;
newCard.appendChild(newCardTitle);
newCard.appendChild(newCardAuthor);
newCard.appendChild(newCardPages);
newCard.appendChild(newCardRead);
}
// Required in order to load saved books onto page
for(let i = 0; i < data.length; i++) {
loadLocalStorage(data[i]);
}