---
# System prepended metadata

title: JavaScript BookList App (可以加入或刪去物件的清單)
tags: [Javascript]

---

# JavaScript BookList App (可以加入或刪去物件的清單)

---
tags: Javascript relate
---

###### tags: `Javascript`

[跟著做出來的範例](https://chiehLiu.github.io/git-projects/MYBOOKLIST/index.html#)


成本圖:

![](https://i.imgur.com/jMC7v71.png)


## 內容概要

* 三個input來做加入書本的動作，輸入內容後會在下方存入它們的資料，並且有刪除的功能，資料可以儲存在網頁上不會被重新整理刷掉
* 會跑出視窗當填寫不完整或是成功執行動作的時候
* 頁面使用簡單的Bootrap框架製作
* 有帶入一些JSON,ES6-class的元素


## HTML+boostrap:



### 前置作業

使用[fontawesome](https://fontawesome.com/)來做icon
```htmlembedded=
<script src="https://kit.fontawesome.com/2ae9e2b502.js" crossorigin="anonymous"></script>
```


使用[bootswatch](https://bootswatch.com/yeti/)來做框架這樣就不用處理CSS瞜


```htmlembedded=
<linkrel="stylesheet"href="https://bootswatch.com/4/ye``ti/bootstrap.min.css">

```
使用範例:

* h1裏面包含了icon的使用fa的部分還有`<i>`tag
* text-primary是bootstrap

```htmlembedded=
<h1 class="display-4">
            <i class="fa fa-book-open text-center text-primary"></i> My<span class="text-primary">Book</span>List</h1>
```

## JS

使用class建構Book這個物件

```javascript=
class Book {
    constructor(title, author, isbn) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }
}
```

接下來會分成數個部分來解釋這整份JS內容:

* UI class:處理有關UI的任務(使用者會看到並且操作的地方)
* Store class:處理storage相關任務
* Event:在list裡面展示書本
* Event:加入書本
* Event:移除書本

-------------------------------------

### UI class

有關於UI的內容會有五個相關的method來處理:

#### displayBooks

新增的書本會放在這個區域下方

![](https://i.imgur.com/SY6qf0A.png)

```javascript=
static displayBooKs() {

        //這邊註解的內容是作者用來一開始當作資料庫使用並且呈現在display區域
        //
        // const StoredBooks = [{
        //         title: 'Book One',
        //         author: 'John Doe',
        //         isbn: '3434434',
        //     },
        //     {
        //         title: 'Book Two',
        //         author: 'Jane Doe',
        //         isbn: '45545',
        //     }
        // ];

        //const books = StoredBooks;

        const books = Store.getBooks();
        // 這個部分是Store部分建立好之後直接引用進來的函式呼叫

        books.forEach((book) => UI.addBookToList(book));
    }   //books的部分會出來陣列(array)所以可以使用forEach逐個去使用addBookTodoList這個方法，來呈現被加進來的書本資料
```


#### addBookToList

書本加入display區域的"方法"呈現在這部分:

```javascript=
static addBookToList(book) {
        // 在#book-list區域下方新增元素<tr>
        const list = document.querySelector('#book-list');
        const row = document.createElement('tr');
        
        
        //新增的內容如下
        row.innerHTML = `
            <td>${book.title}</td>
            <td>${book.author}</td>
            <td>${book.isbn}</td>
            <td><a href="#" class="btn btn-danger btn-sm delete">X</a></td>
        `

        list.appendChild(row);
        // 讓row黏到list下方就是靠這行，appendChild功能是在在列表中添加項目
    }
```

#### clearFields

設定好這個清空功能後在兩個狀態下會清空欄位

* 加入書本成功後會呼叫這個函式動作就會清空欄位詳見(Event: 加入書本)區域
* 重新整理頁面因為預設值為空("")

```javascript=
static clearFields() {
        document.querySelector('#title').value = '';
        document.querySelector('#author').value = '';
        document.querySelector('#isbn').value = '';

    }
```

#### showAlert

一些不熟的方法

[createElement - MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Document/createElement)

[createTextNode - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)

[appendChild - w3cschool](https://www.w3schools.com/jsref/met_node_appendchild.asp)


[setTimeout - MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout)


```javascript=
static showAlert(message, className) {
        //創造一個新的div來產生警告視窗
        const div = document.createElement('div');
        // 幫剛剛創造的div加上bootstrap的屬性產生alert視窗跟顏色message以及className的內容會在下方的Event 加入刪除書本處呼叫並且輸入
        div.className = `alert alert-${className}`;
        //這邊是在撰寫加入meassage部分的程式碼使用appendChild方法加入createTextNode的文字內容
        div.appendChild(document.createTextNode(message));
        
        
        //這個部分處理alert的位置，先設定好指派變數給container以及form，在使用insertBefore安插東西在...之前的概念
        const container = document.querySelector('.container');
        const form = document.querySelector('#book-form');
        container.insertBefore(div, form)//參數(安插物,安插物後方的元素)
        //Vanish in 3 seconds
        //setTimeout計時器方法=>選取要計時的class之後輸入在時間到之後要執行的方法(remove)最後輸入計時時間(毫秒)
        setTimeout(() => document.querySelector('.alert').remove(), 3000)
    }
```
#### deleteBook

這邊兩個parentElement的用意在於位置的關係要砍掉的是`<td>`外面的`<tr>`
![](https://i.imgur.com/0rYVXuv.png)


[contains - w3cschool](https://www.w3schools.com/jsref/met_node_contains.asp)


[classList - MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Element/classList)


[remove - MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/remove)



```javascript=
static deleteBook(el) {
        // 看看元素的class有無包含delete(顯然是有的見上方圖片是X按鈕)，有則執行remove
        if (el.classList.contains('delete')) {
            el.parentElement.parentElement.remove();
        }
    }
```

---
### Store class

處理storage的方法的class，(local storage 也就是browser 除非被刪除不然資料會存在那邊)
並且它不能存object只能存string

#### getBooks

其實就是return傳入的書本的value陣列

[getItem - MDN](https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem)


在四個需要抓取書本的地方出現:

* getBooks方法本身
* UI class displayBooks要呈現書的地方就抓取了這邊的資料
* addBook方法內
* removeBook方法內

```javascript=
static getBooks() {
        let books;
        if (localStorage.getItem('books') === null) {
        //如果loacalStorage抓取books這個鍵的value為空的話值回復一個空的陣列
            books = [];
        } else {
        //如果不為空則回覆取的'books'這個鍵的value
            books = JSON.parse(localStorage.getItem('books'))
        }

        return books;
    }
```

#### addBook
    
它是功能是在localStorage裡面創造鍵與值(key and value)藉由push的方式把參數(book)推進去'books'這個陣列裡面
    
```javascript=
static addBook(book) {
    const books = Store.getBooks();
    books.push(book);
    // push 是在陣列後方加入一個或是多個元素在末端
    localStorage.setItem('books', JSON.stringify(books));
    // setItem(屬性名稱,value(這邊value只能是string所以使用JSON.stringify))
}
```
    
#### removebook
    
得先取得已經加入得書的value，之後根據剛剛的到的資料做做判斷如果符合isbn則執行splice(刪除元素)

```javascript=
static removebook(isbn) {
    //取的書本的value並且assign給books
    const books = Store.getBooks();

    books.forEach((book, index) => {
    //當書本的value傳進來之後，它的陣列index也會傳進來
        if (book.isbn === isbn) {
    //如果書本的isbn是對的話則splice(刪除)index(檢索位置),1個元素就可以完成效果摟!
            books.splice(index, 1)
        }
    });
    //這個部分是reset，因為剛剛刪除完之後再setItem一次重設它的鍵跟值
    localStorage.setItem('books', JSON.stringify(books));
}
```


### 事件監聽(addEventListener)



#### Event: 展示書本在list裡面

讓頁面在重整後依舊可以顯示出書本在list裡面

[DOMContentLoaded - MDN](https://developer.mozilla.org/zh-TW/docs/Web/Events/DOMContentLoaded)

```javascript=
document.addEventListener('DOMContentLoaded', UI.displayBooKs);
```


#### Event: 加入書本

先抓取整個input區域後加入事件監聽在'submit'上，然後使用取得的事件的值(e)，去跑下方判斷式(前提是內容不為空("")

[preventDefault - MDN](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/preventDefault)

[submit_event - MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event)


```javascript=
// Event: 加入書本

//先抓取整個input區域後加入事件監聽在'submit'上，然後使用取得的事件的值(e)去跑下方的方法跟判斷式
document.querySelector('#book-form').addEventListener('submit', (e) => {

    //prevent actual submit
    //讓他提交後就停止動作
    e.preventDefault();

    //Get form values
    //把取得的值assign給變數給下面做資料驗證
    const title = document.querySelector('#title').value;
    const author = document.querySelector('#author').value;
    const isbn = document.querySelector('#isbn').value;


    //Valadate (資料驗證)
    if (title === '' || author === '' || isbn === '') {
        UI.showAlert('Please fill in all fields', 'danger');
    } else {
    
        //instantiate book因為book裡面的資料沒有static所以需要實體化(instantiate)
        const book = new Book(title, author, isbn);

        //經過資料驗證後開始呼叫各個靜態方法(static)

        //Add Book to UI
        UI.addBookToList(book);


        //Add book to store

        Store.addBook(book);

        //Show success message

        UI.showAlert('Book Added', 'success')


        //Clear fields
        UI.clearFields();
    }
})
```

#### Event: 移除書本

一樣抓取完input區域後設定事件監聽使用取得的值(e)，去呼叫下方的靜態方法

UI的部分刪除完成後再刪除Store的部分重新整理頁面就不會再跑回來瞜

```javascript=
document.querySelector('#book-list').addEventListener('click', (e) => {

    //remove books from UI 
    UI.deleteBook(e.target)

    //remove book from store 這段的目的讓刪除的書本不會再重整頁面後再度出現所以要從store裡面刪除
   //e.target的目標是delete button，使用parentElement會到<td>但是目標是要找到isbn所以要找到sibling裡面的textContent
    Store.removebook(e.target.parentElement.previousElementSibling.textContent);

    UI.showAlert('Book Removed', 'success')
})
```