Alysa Chan
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# 【Vue電商】Todo List 練習 ###### tags: `vue` 任務:todo list Udemy:https://www.udemy.com/course/vue-hexschool/learn/lecture/10271404#overview 基本功能: - 按下新增button(或enter時)時會新增代辦事項 - 顯示代辦事項 - 點擊checkbox,代表完成代辦事項,該代辦事項會被劃掉(切換劃線的css style) - 點擊「全部」(預設是在全部): 會顯示**全部**代辦事項; 點擊「進行中」: 會顯示**未完成**代辦事項; 點擊「已完成」: 會顯示**完成**代辦事項; - 按下「x」,會刪除該代辦事項 - 按下「清除所有任務」,會刪除所有代辦事項 - 右下會顯示還有多少筆任務**未完成** 進階功能: - double click代辦事項時,會切換為輸入欄 - 在輸入欄按enter後:會修改代辦事項的title - 在輸入欄按esc後:會退出修改代辦事項的title ## 按下新增button(或enter時)時會新增代辦事項 1. 在最上方的輸入欄,即是`<input>`使用`v-model`,此`v-model`是連接vue的data裏的`newTodo`,讓我們能夠在`newTodo`儲存使用者所輸入的代辦事項。 2. 在「新增」button設定`@click="addTodo"`,以及在`<input>`欄位設定`@keyup.enter="addTodo"`。作用是當使用者按下新增或enter鍵時,就執行`addTodo`函式。 ```htmlembedded= <!-- 把使用者輸入的代辦事項儲存到vue的newTodo --> <!-- 當使用者按下新增或enter鍵時,就執行addTodo --> <input type="text" class="form-control" placeholder="準備要做的任務" v-model="newTodo" @keyup.enter="addTodo"> <div class="input-group-append"> <button class="btn btn-primary" type="button" @click="addTodo">新增</button> </div> ``` Vue: ```javascript= var app = new Vue({ el: '#app', data: { // 存放使用者輸入的新代辦事項 newTodo: "", // 所有代辦事項 todos: [], }, } ``` 3. `addTodo`函式的作用是用`.push`方法,把新增的代辦事項加進`todos`裏。每筆代辦事項都需要有: - id - title - completed (該事項是否完成) 因此,`addTodo`的函式應該是這樣: ```javascript= methods:{ addTodo: function(){ this.todos.push({ id: Math.floor(Date.now()), title: this.newTodo, // 預設是false completed: false, }) // 清空輸入欄 this.newTodo = ""; } ``` ### 為什麼需要`id`? 因為每個代辦事項左邊都有個checkbox,該代辦事項的id需要與該checkbox的id相同,才可以使我們點擊該代辦事項的title時,就可以對應點擊到該checkbox。因此,每個代辦事項都要有一個獨有的id。 ### 用`Date.now()`方法產生id `Date.now()`會產生一組數字,該數字是自 1970/01/01 00:00:00 UTC 起經過的毫秒數。之後用`Math.floor()`(取小於這個數的最大整數)去取整數。 ### 顯示代辦事項 ```htmlembedded= <!-- 使用v-for撈取所有在todos裏的代辦事項 --> <ul class="list-group list-group-flush text-left" v-for="item in todos"> <li class="list-group-item"> <div class="d-flex" @dblclick="editTodo(item)"> <div class="form-check"> <!-- 用:id和:for 綁定該代辦事項的id --> <!-- 用v-model來操控該代辦事項是否已被完成 --> <input type="checkbox" class="form-check-input" :id="item.id" v-model="item.completed"> <label class="form-check-label" :for="item.id"> {{ item.title }} </label> </div> ... </ul> ``` #### 在checkbox使用`v-model` 在checkbox使用`v-model`,會得出boolean值。因此可以操控該代辦事項的complated狀態是`true`還是`false`。 詳情:https://vuejs.org/v2/guide/forms.html #### `v-bind`和`v-on`縮寫 `v-bind`縮寫: 用`:`。例如:`:class=""`、`:id=""` `v-on`縮寫: 用`@`。例如:`@click=""`、`@dblclick=""`、`@keyup.enter=""` ## 防止輸入空白值(自己做的時候漏掉這部分) 為了防止使用者新增空白的代辦事項,我們需要在`addTodo`函式裏做判斷。以下修改一下`addTodo`函式: ```javascript= addTodo: function(){ // 用trim方法刪掉多餘的空白 const value = this.newTodo.trim(); // 避免輸入空白的代辦事項 if(!value){ return }; this.todos.push({ // 以下是自己的寫法,自己寫時是用new Date // id: Math.floor(new Date), // 但老師建議這樣寫 id: Math.floor(Date.now()), title: value, completed: false, }) // 清空輸入欄 this.newTodo = ""; } ``` ## 按下「x」後會刪除資料 <!-- 正確做法: 把該代辦事項的id傳進來,之後用該id找尋該代辦事項在todo陣列中的index,最後用`splice()`方法刪除。 ```htmlembedded= <button type="button" class="close ml-auto" aria-label="Close" @click="deleteTodo(item.id)">...</button> ``` ```javascript= ... methods:{ deleteTodo: function(id){ let todoIndex = this.todos.findIndex(item => item.id === id); this.todos.splice(todoIndex, 1); } } ``` 看看以下做法: --> <!-- 正確做法: ```htmlembedded= <button type="button" class="close ml-auto" aria-label="Close" @click="deleteTodo(item.id)">...</button> ``` ```javascript= ... methods:{ deleteTodo: function(id){ let todoIndex = this.todos.findIndex(item => item.id === id); this.todos.splice(todoIndex, 1); } } ``` --> 先寫以下做法: 這個做法是把該事項的index直接傳進函式裏,並用它來刪掉資料。 ```htmlembedded= <li class="list-group-item" v-for="(item, key) in filterTodo"> ... <button type="button" class="close ml-auto" aria-label="Close" @click="deleteTodo(key)">...</button> </li> ``` ```javascript= ... methods:{ deleteTodo: function(key){ this.todos.splice(key, 1); } } ``` 但以上做法會之後出現bug,請在讀完頁籤分類效果的filterTodo部分後,再跳回來以下部分! 為什麼會有bug?因為當我們轉到其他頁籤時,該頁籤的陣列資料(filterTodo)會不同於原始陣列(todos)。 例如以下情況,我新增了1,2,3,4,完成了2,3,之後我按下已完成頁籤,想刪除3,你會發現3並沒有被刪掉,反而是2被刪除: ![](https://i.imgur.com/VxvUYl8.gif) 這是因為在filterTodo陣列裏,第2筆資料是「3」,但是在todos這個原始陣列裏,第2筆資料是「2」,所以反而是「2」被刪掉了。 因此,要改用以下的寫法: ```htmlembedded= <!-- 改為傳送item.id --> <button type="button" class="close ml-auto" aria-label="Close" @click="deleteTodo(item.id)">...</button> ``` ```javascript= methods:{ deleteTodo: function(id){ let todoIndex = this.todos.findIndex(item => item.id === id); this.todos.splice(todoIndex, 1); } } ``` 這個方法就是把該代辦事項的id傳進來`deleteTodo`,然後在`deleteTodo`裏,利用傳進來的id,找尋此id在原始陣列todos裏的index(我命名為todoIndex),最後利用todoIndex在todos陣列裏刪除該筆代辦事項。 這個做法就成功解決那個bug了。 ## 把已完成的代辦項目加上劃線的class 當使用者完成該代辦事項時,該項目會有劃線效果: ![](https://i.imgur.com/8exq1x7.png) CSS樣式: ```css= .completed { text-decoration: line-through } ``` ```htmlmixed= <!-- 切換completed CSS樣式 --> <label class="form-check-label" :for="item.id" :class="{'completed': item.completed}"> {{ item.title }} </label> ``` 當該代辦事項的completed狀態是true時,就會加上`completed` CSS樣式。 ## 頁籤分類效果(自己做時這裏有卡關) ![](https://i.imgur.com/KIVQA5h.png) - 點擊進行中:就只會顯示未完成的項目。 - 點擊已完成:就只會顯示已完成的項目。 - 點擊全部:就會顯示全部項目(一載入網頁後,預設是全部) 以上這些功能,可以大概猜出這裏需要一個過濾功能,例如: - 點擊已完成 --> 只撈已完成的資料 (completed狀態是 true) - 點擊進行中 --> 只撈進行中的資料 (completed狀態是 false) 但我們不需要每個狀態都開一個新陣列,例如開一個叫`activeTodos`、`completedTodos`,把完成和未完成的項目從todos裏面抽取來,並分別push進這兩個陣列裏,這是很累贅的做法。 我們可以這樣想: - todos 陣列 這是原本放置所有代辦事項的陣列,我們**不要動這個陣列**。 - filterTodos 陣列 這是一個**會變動**的陣列。它要做的事就是,**看你點擊到哪個頁籤,它就按哪個頁籤去過濾todos陣列,並回傳過濾好的資料。** 如何看你點擊哪個頁籤?我們可以在`data`裏設定一個變數,該變數會顯示目前使用者在哪個頁籤,我們把該變數取命為`visibility`: ```javascript= data: { newTodo: "", todos: [], visibility: 'all', } ``` 在HTML的部分,設定當該頁籤被點擊時,就會更改`visibility`的變數值: ```htmlembedded= <li class="nav-item"> <a class="nav-link" href="#" :class="{ 'active' : visibility === 'all'}" @click.prevent="visibility = 'all'" >全部</a> </li> <li class="nav-item"> <a class="nav-link" href="#" :class="{ 'active' : visibility === 'active'}" @click.prevent="visibility = 'active'" >進行中</a> </li> <li class="nav-item"> <a class="nav-link" href="#" :class="{ 'active' : visibility === 'completed'}" @click.prevent="visibility = 'completed'" >已完成</a> </li> ``` > 別忘記click後面要加上`.prevent`,因為`<a>`本身有連結功能,預設是會跳到href裏的地址,所人要用`.prevent`取消跳轉到連結,這等同於之前學JS時的`.preventDefault()`。 另外,在頁籤的部分,當該頁籤被點擊後,就會被加上`active`這個CSS樣式,例如我點擊了「進行中」後,「進行中」被框起來,以及文字變灰色: ![](https://i.imgur.com/s50LLxp.png) 所以以上程式碼中,也用`:class`綁定`active`這個CSS樣式。 回到Vue的部分,這時候我們會用到`computed`,重溫`computed`特性: - 有緩存機制,資料沒變動的話,就不會重新渲染 `methods`是**沒有緩存機制**,**不管資料有沒有更新,它都會重新渲染**。 在我們的情況,因為當todos陣列(即是放置所有代辦項目的那個陣列)裏有項目被checked,即是被標記為完成,理論上,「進行中」和「已完成」頁面就需要被更新,「進行中」會顯示少了一個項目,「已完成」會顯示多了一個項目。 反之,todos陣列裏沒有變動,就不用重新渲染畫面。 所以這裏用`computed`。 ```javascript= computed: { filterTodo: function(){ if(this.visibility === 'all'){ return this.todos; } else if(this.visibility === 'active'){ return this.todos.filter( item => item.completed === false); }else if(this.visibility === 'completed'){ return this.todos.filter( item => item.completed === true); } } } ``` 之後我們要改成在`filterTodo`裏撈資料,而非原始陣列`todos`: ```htmlembedded= <li class="list-group-item" v-for="(item, key) in filterTodo"> ... </li> ``` ## 雙擊編輯代辦事項(自己做時這裏有卡關) 接下來要做的功能就是當使用者雙擊該代辦事項時,就會切換成輸入欄位,讓使用者修改該代辦事項的title,如下圖: ![](https://i.imgur.com/sz5cuNw.gif) 這個效果有幾個功能要做: - 修改該代辦事項的title - 要**隱藏**原本的代辦事項,並**打開**input輸入欄 - 修改完後,按enter儲存,按esc就退出和不儲存修改 ### 修改該代辦事項的title 利用`@dblclick=""`來設定雙擊事件,並且執行`editTodo`函式。 html的部分: ```htmlembedded= <div class="d-flex" @dblclick="editTodo(item)"> ``` Vue的部分,我們將該正在被修改的那筆代辦事項以及它的title暫存起來: ```javascript= data: { newTodo: "", todos: [], visibility: 'all', // 以下變數會暫時存放該正在被修改的代辦事項 cacheTodo: {}, cacheTitle: '' } ``` ```javascript= methods:{ ..., editTodo: function(item){ // 把傳進來的item資料存起來 // 才可以知道目前哪筆資料正在被編輯中 this.cacheTodo = item; //cacheTitle會用來作編輯時預設顯示的內容 this.cacheTitle = item.title; // 以上暫存的做法可以達成一次只能編輯一個item, // 因為每當item被雙擊時,cacheTodo 和 cacheTitle 都會被更新, // 並指向該被雙擊的item。因此不會出現同時修改多個item的情況 } } ``` 以下會再解釋為什麼要暫存該筆代辦事項的資料。 ### 隱藏原本的代辦事項,並打開input輸入欄 雙擊後,就要**隱藏**原本的代辦事項,並**打開**input輸入欄。我們可用`v-if`去寫出這個效果: ```htmlembedded= <li class="list-group-item" v-for="(item, key) in filterTodo"> <div class="d-flex" @dblclick="editTodo(item)" v-if="item.id !== cacheTodo.id"> <div class="form-check"> <input type="checkbox" class="form-check-input" :id="item.id" v-model="item.completed"> <!-- 切換completed CSS樣式 --> <label class="form-check-label" :for="item.id" :class="{'completed': item.completed}"> {{ item.title }} </label> </div> <button type="button" class="close ml-auto" aria-label="Close" @click="deleteTodo(key)"> <span aria-hidden="true">&times;</span> </button> </div> <!-- 如果這個item的id與cacheTodo的id相同,就代表這個item正在被編輯中,因此要顯示以下的input欄位 --> <input type="text" class="form-control" v-if="item.id === cacheTodo.id"> </li> ``` 以上程式碼解釋了為什麼剛剛我們需要暫存該筆事項,因為在這裏的隱藏/顯示功能中,**需要用該筆事項去做判斷**: 如果這個代辦事項的id 與 暫存中的代辦事項id相同,就代表這個代辦事項正在被編輯,所以要**隱藏**原本的資料欄,並**打開**輸入欄位。同一道理,如果該代辦事項的id 不等於 暫存的id,那就意味該筆代辦事項並沒有正在被修改,所以就**隱藏**輸份欄,**只顯示**資料欄。 這裏用`v-if`去寫出效果,`v-if`的用法就是只要條件是true,就會渲染該元素。 ### `v-if` VS `v-show` 題外話: - `v-if`:條件是true才會渲染 - `v-show`:不管條件是否true都會渲染,並用CSS`display`屬性去隱藏或顯示。 所以: - 如果經常需要切換,就用`v-show` - 如果不常切換,就用`v-if` 詳細看:https://vuejs.org/v2/guide/conditional.html ### 雙擊修改時,預設會先顯示原本的title 重看上面那張gif,可見雙擊代辦事項,並切換到輸入欄後,輸入欄會預設顯示該代辦項目原本的title(這個gif例子中就是「1」),而非空白: ![](https://i.imgur.com/sz5cuNw.gif) 所以,我們需要在輸入欄位,預設會顯示該筆事項的title。這裏使用`v-model`去把title顯示出來: ```htmlembedded= <input type="text" class="form-control" v-if="item.id === cacheTodo.id" v-model="cacheTitle"> ``` ### 按esc後取消編輯,按enter儲存編輯 ```htmlembedded= <input type="text" class="form-control" v-if="item.id === cacheTodo.id" v-model="cacheTitle" @keyup.esc="cancelEdit" @keyup.enter="finishEdit(item)" > ``` Vue部分: ```javascript= methods:{ ... cancelEdit: function(){ this.cacheTodo = {}; } } ``` 以上做法就是清空暫存的代辦事項,作用就是退出編輯。 ```javascript= methods:{ finishEdit: function(item){ // 更改該item的title item.title = this.cacheTitle; // 清空cacheTodo,否則input欄位不會隱藏起來 this.cacheTodo = {}; // 問題:以下這段不加也行?因為都是看<input>和<div>都只是看item.id去判斷。而下次修改另一筆資料時,cacheTitle會被替換掉 this.cacheTitle = ''; } } ``` ## 顯示還剩下多少未完成項目 ```htmlembedded= <div class="card-footer d-flex justify-content-between"> <span>還有 {{ countActiveTodo }} 筆任務未完成</span> </div> ``` ```javascript= computed:{ ... countActiveTodo: function(){ return this.todos.filter( item => item.completed === false).length; } } ``` ## 刪除所有資料 ```htmlembedded= <div class="card-footer d-flex justify-content-between"> <span>還有 {{ countActiveTodo }} 筆任務未完成</span> <a href="#" @click.prevent="deleteAllTodo">清除所有任務</a> </div> ``` ```javascript= methods:{ ... deleteAllTodo: function(){ this.todos = []; } } ``` ## 完整程式碼 https://codepen.io/alysachan/pen/bGevJBV

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully