# 用VUE寫 TO DO LIST & 表單驗證 > 參考範例: > [Vue To-do List 拆解練習](https://medium.com/ivycodefive/vue-to-do-list-%E6%8B%86%E8%A7%A3%E7%B7%B4%E7%BF%92-8c6f079919e2) > [自己的CodePen](https://codepen.io/ntjtcxpt-the-animator/pen/mdpeJEj) ## 拆解步驟 1. 新增 List 2. 切換checkbox 3. 刪除 List 4. 全部刪除 5. 事項分類 6. 目前剩下幾個代辦項目 ## 1. 新增 List + 流程: 1. js上定義`newTodo「字串」`,目的是將input所輸入的value放入newTodo中 2. js上定義`allData「陣列」`,目的是將newTodo放入allData 3. 在js綁定方式:`addList()` 1. 將`value: this.newTodo` `id: Math.random()` `isChecked: false` 使用`push` 方式放入 `allData陣列中` 2. 最後在寫`this.newTodo = ""`,目的是為了把每新增一次就把字串清空 3. 記得加入防呆,以防萬一輸入空字串 4. 在html的form上寫`@submit.prevent="addList"`,且在input上綁定 `v-model.trim="newTodo" ` `:value="newTodo"`,當按下 **enter** or **button type="submit"** 即會送出資料到 addList 上 5. li使用`v-for="item in allData"` 使資料可以綁定在html畫面上 ```js data() { return { newTodo: "", allData: [ { value: "測試:未完成", id: Math.random(), isChecked: false, }, { value: "測試:已完成", id: Math.random(), isChecked: true, } ], }; }, methods: { addList() { // 避免輸入空字串 if (this.newTodo === "") { return alert("記得輸入一些東西唷!"); } this.allData.push({ value: this.newTodo, // input所輸入的值 id: Math.random(), // 產生亂數(目的:不要有重複id) isChecked: false, // checkbox預設是false }); this.newTodo = ""; // 淨空input this.currentTab = "全部"; // 使每新增一次就跳回全部標籤 }, }, ``` ```html <form @submit.prevent="addList"> <input type="text" placeholder="新增待辦事項" v-model.trim="newTodo"> <button type="submit"> <img class="me-2" src="image/plus 1.png" alt="plus"> </button> </form> <li v-for="item in allData" :key="item.id"> <label class="checkbox" :for="item.id"> <input class="check" type="checkbox" :id="item.id" v-model="item.isChecked"> // 如果要套用unclick,isChecked要是false,且要將false反轉才會套用 <span :class="{'unclick':!item.isChecked}"></span> // 如果要套用clicked,isChecked要是true <span :class="{'clicked':item.isChecked}"></span> <span :class="{'checked':item.isChecked}">{{ item.value }}</span> <input :id="item.id" type="button" value="×"> </label> </li> ``` ## 2. 切換checkbox + 流程:程式碼如同 `新增List` 的,且從第五點開始一起看 1. 在label上綁定`:for="item.id"`,且在input上也綁定`:id="item.id"`,目的是為了讓 label & input 做互相對應 2. input綁定`v-model="item.isChecked"`,目的是為了將 **isChecke** 做雙向綁定 3. 使v-bind綁定css樣式,但因為 `樣式:true` 才會套用,所以要將unclick的false反轉才行(詳細請看上面程式碼註解,但解釋的有點饒舌) ## 3. 刪除 List + 流程: 1. 在js綁定方式 `deleteList(目前所點到的)`:全部資料=全部資料去篩選出,全部資料的id != 目前所點到的id (用排除方式寫:留下不是目前點到的,刪除目前所點到的) 2. html綁定 `@click="deleteList(item)"` ```js deleteList(key) { // 不知為何用這方法會出錯,所以還是改用篩選方式寫 // this.allData.splice(key, 1); this.allData = this.allData.filter((item) => { console.log(item.id) return item.id !== key.id; }); this.currentTab = "全部"; }, ``` ```heml <li v-for="item in allData" :key="item.id"> <label class="checkbox" :for="item.id"> ... <input :id="item.id" class="cancel" type="button" value="×" @click="deleteList(item)"> </label> </li> ``` ## 4. 全部刪除 + 流程: 1. 使用computed讀取doneData 2. 將全部資料去篩選出 isChecked === false `因為要將非完成List放入到完成陣列中` 3. js綁定方式:`deleteAll()` 當全部資料 = doneData 4. 在html上綁定 `@click="deleteAll"` ```js methods: { deleteAll() { // 將尚未完成的List放入到全部資料中(用排除法方式:留下非完成,刪掉已完成) this.allData = this.doneData; // 每按一次就跳回 全部 標籤上 this.currentTab = "全部"; }, }, computed: { doneData() { return this.allData.filter((item) => { // 要取出尚未完成的List return item.isChecked === false; }); }, }, ``` ```html <div> ... <p v-if="workData.length !==0">{{workData.length}} 個待完成項目</p> <a href="#" class="text-dark" @click="deleteAll">清除已完成項目</a> </div> ``` ## 5. 事項分類 1. 切換上方Tabs + 流程: 1. 先在js上定義好currentTab(預設值是**全部**) 2. 把tab標籤名稱放入 js data 中(使用陣列方式) 3. 在html上使用 `v-for="tab in tabs" :key="tab"` ,讓標籤綁定在html上,並加上 `{{ tab }}` 使標籤可以顯示在畫面 4. 在js綁定方式:`selectTab(item) { this.currentTab = item; }` 5. 在html上綁定 `@click="selectTab(tab)` ,使目前點到的tab可以放入步驟四的`this.currentTab`上 6. 再加上 `:class="{'tab-active':currentTab===tab}` ,當 `currentTab===tab` 就套用 `tab-active` 樣式 ```js data() { return { currentTab: "全部", tabs: ["全部", "待完成", "已完成"], }; }, methods: { selectTab(item) { this.currentTab = item; }, }, ``` ```html <ul class="text-center d-flex list-unstyled mb-0 text-dark fw-bold"> <li class="tab w-100 pb-3 border-bottom border-2" :class="{'tab-active':currentTab===tab}" v-for="tab in tabs" :key="tab" @click="selectTab(tab)" >{{ tab }} </li> </ul> ``` 2. 切換tabs & 下方的事項 + 流程: 1. 使用computed做讀取內容 > computed:把data的內容讀取出來→重新運算一個新的結果→再渲染到畫面上 > p.s. computed 一定要寫 **return** 才會回傳到畫面上 2. 把原本的`v-for="item in allData" ` 改成 `v-for="item in filterData"` ```js data() { return { ... selectData: [], // 存放篩選的資料 }; }, computed: { filterData() { // 如果 currentTab === "已完成",將全部資料做篩選,選出 isChecked === true ,並放入 selectData 中 if (this.currentTab === "已完成") { return (this.selectData = this.allData.filter((item) => { return item.isChecked === true; })); // 如果 currentTab === "待完成",將全部資料做篩選,選出 isChecked === false ,並放入 selectData 中 } else if (this.currentTab === "待完成") { return (this.selectData = this.allData.filter((item) => { return item.isChecked === false; })); // 如果 currentTab !== 以上的(意思就是currentTab === "全部"),將全部資料 放入 selectData 中 } else { return (this.selectData = this.allData); } }, }, ``` ```html <!-- 原本 --> <li v-for="item in allData" :key="item.id"> ... </li> <!-- 改成 --> <li v-for="item in filterData" :key="item.id"> ... </li> ``` ## 6. 目前剩下幾個代辦項目 + 流程: 1. 使用computed做讀取內容 2. 將全部資料做篩選,選出 isChecked === false 3. 再將篩選出來的資料長度,放入 html 畫面上 4. 如果`workData.length !==0"`則套用第一個,若`workData.length ===0"`則套用第二個 ```js computed: { workData() { return this.allData.filter((item) => { return item.isChecked === false; }); }, } ``` ```html <!-- 第一個 --> <p v-if="workData.length !==0">{{workData.length}} 個待完成項目</p> <!-- 第二個 --> <p v-if="workData.length ===0">恭喜全部已完成!</p> ``` ## 理解 1.submit要用form(不懂用背的QQ) > 參考文件: > [vue.js](https://v3.cn.vuejs.org/api/directives.html#v-on) > [Vue.js Core 30天屠龍記(第16天): 事件處理](https://ithelp.ithome.com.tw/articles/10206798) ## 表單驗證 + 流程: 1. js綁定方式:`login()` ```js data() { return { emailError: false, passwordError: false, myListLink:'#', loginInputValue: {}, loginEmail: "", loginPassword: "", }, methods: { login() { if (this.loginEmail === "" && this.loginPassword === "") { this.emailError = true; this.passwordError = true; } else if (this.loginEmail ==="") { this.emailError = true; this.passwordError=false } else if (this.loginPassword ==="") { this.passwordError = true; this.emailError = false; } else{ this.myListLink="todolist.html" } }, }, ``` ```html <div class="input-group-lg"> <label for="Email"> Email </label> <input v-model="loginEmail" placeholder="請輸入Email" id="Email" type="email"> <div class="invalid-feedback" :class="{'d-block':emailError}">請輸入正確格式</div> </div> <div class="input-group-lg"> <label for="password"> 密碼 </label> <input v-model="loginPassword" placeholder="請輸入密碼" id="password" type="password"> <div class="invalid-feedback" :class="{'d-block':passwordError}">此欄位不得為空</div> </div> <div> <a :href="myListLink" type="submit" class="btn btn-secondary my-3" @click="login">登入</a> <a href="register.html" class="text-center fw-normal">註冊帳號</a> </div> ``` > [表單驗證,正規表達](https://hackmd.io/@FortesHuang/rJf6CYynS#Email%E9%A9%97%E8%AD%89%E7%9A%84%E8%A4%87%E9%9B%9C%E6%80%A7) >