# 11/17 學員操作手冊 --- 專案下載 https://ppt.cc/f0MAqx --- ### Step 01 專案使用特定的 prototype (10 min) 1. 創建一個 TodoList.vue, 只留下 template script style tag 2. 打開prototype, 將 index.html 的 ```<section class="todoapp">``` 區塊複製到 TodoList.vue 的 template 3. 將 App.vue 內的 HelloWorld 元件改為 TodoList 元件 4. assets 建立css folder, 建立 base.css & index.css 5. import base.css & index.css 到 App.vue (不是TodoList.vue) --- ### Step 02 將頁面切分成多個子元件 (5 min) 1. 建立 TitleComponent.vue 2. 命名元件, 並了解建立元件的命名規則 3. 將 ```<h1>todos</h1>``` 貼到 template 4. 回到 TodoList.vue import TitleComponent元件 --- ### Step 03 快速修改頁面上的文字 (10 min) 1. 把 TitleComponent h1 裡面的 hardcode 改成動態的 title by 雙括弧 2. 把 TodoList.vue 的 input placeholder 透過 v-bind 的方式改變內容 3. 簡化 v-bind 撰寫方式 --- ### Step 04 事件繫結 Event Binding (15 min) 1. data 新增 keyinItemList: [], 幫input 加上v-model="keyinItem", 同時data 也新增 keyinItem: null 2. 透過雙括弧展示目前已可取到值 3. input 加上 v-on:keyup.enter="keyinTodo()" , 新增 methods, 新增 keyinTodo方法 4. 簡化v-on 的撰寫方式 5. keyinTodo method裡面 把資料寫入keyinItemList, 寫入後清除keyinItem 的值, 最後透過 {{keyinItemList}} 展示目前已可取到值 --- ### Step 05 使用結構語法(10 min) 1. 把版面作調整, mark掉部分HTML, 注意v-for 用法, 包含後面的 :key="index" 2. 在 ```<button class="destroy"> 新增 @click="removeItem(index)" ``` 然後在 methods 新增 removeItem 3. 判斷當清單是空的時候, 隱藏最底下區塊 --- ### Step 06 語法運用(15 min) 1. 改變 keyinItemList 結構, 增加 isChecked 屬性 2. 新增 computed 屬性 remaining 3. 透過樣式綁定修正 class 來達成"已完成"項目 4. 透過 v-if 去控制右下角 Clear completed, 並完成該功能 5. 透過 computed 實作 get set 完成左上角的 toggle-all --- ### Step 07 根據條件篩選結果 (15 min) * 在 export default 外面新增 filters 方法 * data 新增 visibility 參數 * method 新增 onHashChange 方法 * 新增生命週期 mounted * 樣式綁定被選擇的 all / active / completed * computed 新增 filterItemList * 將 v-for 的 keyinItemList 替換成 filterItemList --- ### Step 08 元件溝通 (10 min) 1. TodoList.vue 的 script 新增 props, template 新增一個接收 props 的變數: createAuthor 2. props 官方推薦的正確用法 create-author * https://v3.vuejs.org/style-guide/ 3. TodoList.vue 的 method 新增 sendToParent, 裡面使用$emit 去觸發自定義的父親事件 from-child, template 新增 sendToParent 方法 App.vue 的 template 新增 @from-child 自定義事件, 然後 script新增一個 reveiveMsg 去承接 @from-child --- ### 程式碼 --- #### 1-1 檔案: TodoList.vue ```javascript= <template> </template> <script> export default { } </script> <style> </style> ``` --- #### 1-2 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="What needs to be done?" autofocus> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> <input class="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <li class="completed"> <div class="view"> <input class="toggle" type="checkbox" checked> <label>Taste JavaScript</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> <li> <div class="view"> <input class="toggle" type="checkbox"> <label>Buy a unicorn</label> <button class="destroy"></button> </div> <input class="edit" value="Rule the web"> </li> </ul> </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>0</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed">Clear completed</button> </footer> </section> </template> <script> export default { } </script> <style> </style> ``` --- #### 1-3 檔案: APP.vue ```javascript= <template> <img alt="Vue logo" src="./assets/logo.png"> ==>移除 <HelloWorld msg="Welcome to Your Vue.js App"/> ==> 移除 </template> <script> import HelloWorld from './components/HelloWorld.vue' ==>移除 export default { name: 'App', components: { HelloWorld ==> 移除 } } </script> ``` <br/> 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <h1>todos</h1> <input class="new-todo" placeholder="What needs to be done?" autofocus> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> <input class="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <li class="completed"> <div class="view"> <input class="toggle" type="checkbox" checked> <label>Taste JavaScript</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> <li> <div class="view"> <input class="toggle" type="checkbox"> <label>Buy a unicorn</label> <button class="destroy"></button> </div> <input class="edit" value="Rule the web"> </li> </ul> </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>0</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed">Clear completed</button> </footer> </section> </template> <script> export default { name: 'TodoList' } </script> <style> </style> ``` --- #### 2-1 檔案: TitleComponent.vue ```javascript= <template> </template> <script> </script> <style> </style> ``` --- #### 2-2 檔案: TitleComponent.vue ```javascript= <script> export default { name: 'TitleComponent' } </script> ``` --- #### 2-3 檔案: TitleComponent.vue ```javascript= <template> <h1>Todos</h1> </template> ``` --- #### 2-4 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" placeholder="What needs to be done?" autofocus> </header> . . . . </section> </template> <script> import TitleComponent from './TitleComponent.vue' export default { name: 'TodoList', components: { TitleComponent } } </script> ``` --- #### 3-1 檔案: TitleComponent.vue ```javascript= <template> <h1>{{title}}</h1> </template> <script> export default { name: 'TitleComponent', data() { return { title: 'Chris Todos' } } } </script> ``` --- #### 3-2 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" v-bind:placeholder="thePlaceholder" autofocus> </header> . . . <template> <script> import TitleComponent from './TitleComponent.vue' export default { name: 'TodoList', components: { TitleComponent }, data() { return { thePlaceholder: '請輸入代辦事項' } } } </script> ``` --- #### 3-3 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" :placeholder="thePlaceholder" autofocus> </header> . . . <template> ``` --- #### 4-1 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" autofocus> </header> . . . . </template> export default { name: 'TodoList', components: { TitleComponent }, data() { return { thePlaceholder: '請輸入代辦事項', keyinItemList: [], keyinItem: null } } } ``` --- #### 4-3 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent>{{keyinItem}} <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" v-on:keyup.enter="keyinTodo()" autofocus> </header> . . . . </template> <script> import TitleComponent from './TitleComponent.vue' export default { name: 'TodoList', components: { TitleComponent }, data() { return { thePlaceholder: '請輸入代辦事項', keyinItemList: [], keyinItem: '' } }, methods: { keyinTodo () { this.keyinItem += '好~'; } }, } </script> ``` --- #### 4-4 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent>{{keyinItem}} <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" @keyup.enter="keyinTodo()" autofocus> </header> . . . . </template> ``` --- #### 4-5 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent>{{keyinItemList}} <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" @keyup.enter="keyinTodo()" autofocus> </header> . . . . </template> <script> . . . methods: { keyinTodo () { const _value = this.keyinItem && this.keyinItem.trim(); if(_value) { this.keyinItemList.push(_value) this.keyinItem = null; } } }, . . . </script> ``` --- #### 5-1 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" @keyup.enter="keyinTodo()" autofocus> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> <input class="toggle-all" type="checkbox"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <!-- <li class="completed"> <div class="view"> <input class="toggle" type="checkbox" checked> <label>Taste JavaScript</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> --> <li v-for="(item, index) in keyinItemList" :key="index"> <div class="view"> <input class="toggle" type="checkbox"> <label>{{item}}</label> <button class="destroy"></button> </div> <!-- <input class="edit" value="Rule the web"> --> </li> </ul> </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>0</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a class="selected" href="#/">All</a> </li> <li> <a href="#/active">Active</a> </li> <li> <a href="#/completed">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed">Clear completed</button> </footer> </section> </template> <script> import TitleComponent from './TitleComponent.vue' export default { name: 'TodoList', components: { TitleComponent }, data() { return { thePlaceholder: '請輸入代辦事項', keyinItemList: [], keyinItem: '' } }, methods: { keyinTodo () { const _value = this.keyinItem && this.keyinItem.trim(); if(_value) { this.keyinItemList.push(_value) this.keyinItem = null; } } }, } </script> ``` --- #### 5-2 檔案: TodoList.vue ```javascript= <template> . . . <div class="view"> <input class="toggle" type="checkbox"> <label>{{item}}</label> <button class="destroy" @click="removeItem(index)"></button> </div> . . . </template> <script> . . . methods: { keyinTodo () { const _value = this.keyinItem && this.keyinItem.trim(); if(_value) { this.keyinItemList.push(_value) this.keyinItem = null; } }, removeItem(index) { this.keyinItemList.splice(index, 1); } }, . . . </script> ``` --- #### 5-3 檔案: TodoList.vue ```javascript= <template> . . <footer class="footer" v-if="keyinItemList.length"> . . </template> ``` --- #### 6-1 檔案: TodoList.vue ```javascript= <template> . . <div class="view"> <input class="toggle" type="checkbox"> <label>{{item}}</label> <label>{{ item.text }}</label> <button class="destroy" @click="removeItem(index)"></button> </div> . . </template> <script> . . methods: { keyinTodo () { const _value = this.keyinItem && this.keyinItem.trim(); if(_value) { this.keyinItemList.push({ text: _value, isChecked: false }) this.keyinItem = null; } }, . . </script> ``` --- #### 6-2 檔案: TodoList.vue ```javascript= <template> . . <span class="todo-count"><strong>{{ remaining }}</strong> item left</span> . . </template> <script> . . computed: { remaining() { return this.keyinItemList.filter( item => { return !item.isChecked}).length; } }, . . </script> ``` --- #### 6-3 檔案: TodoList.vue ```javascript= <template> . . . <li v-for="(item, index) in filterItemList" :key="index" :class="{ 'completed': item.isChecked}"> <div class="view"> <input class="toggle" type="checkbox" v-model="item.isChecked" @click="item.isChecked=!item.isChecked"> <label>{{ item.text }}</label> <button class="destroy" @click="removeItem(index)"></button> </div> <!-- <input class="edit" value="Rule the web"> --> </li> . . . </template> ``` --- #### 6-4 檔案: TodoList.vue ```javascript= <template> . . . <button class="clear-completed" v-if="keyinItemList.length > remaining" @click="clearCompleted()">Clear completed</button> . . . </template> <script> . . . // methods新增以下方法 clearCompleted() { this.keyinItemList = this.keyinItemList.filter( item => { return !item.isChecked}); }, . . . </script> ``` --- #### 6-5 檔案: TodoList.vue ```javascript= <template> . . . <input class="toggle-all" type="checkbox" v-model="allDone" v-if="keyinItemList.length"> . . . </template> <script> . . . //computed新增以下方法 allDone: { get() { return this.remaining === 0; }, set(value) { this.keyinItemList.forEach((item) => { item.isChecked = value; }); } } . . . </script> ``` --- #### 7 檔案: TodoList.vue ```javascript= <template> <section class="todoapp"> <header class="header"> <TitleComponent></TitleComponent> <input class="new-todo" :placeholder="thePlaceholder" v-model="keyinItem" @keyup.enter="keyinTodo()" autofocus> </header> <!-- This section should be hidden by default and shown when there are todos --> <section class="main"> <input class="toggle-all" type="checkbox" v-model="allDone" v-if="keyinItemList.length"> <label for="toggle-all">Mark all as complete</label> <ul class="todo-list"> <!-- These are here just to show the structure of the list items --> <!-- List items should get the class `editing` when editing and `completed` when marked as completed --> <!-- <li class="completed"> <div class="view"> <input class="toggle" type="checkbox" checked> <label>Taste JavaScript</label> <button class="destroy"></button> </div> <input class="edit" value="Create a TodoMVC template"> </li> --> <li v-for="(item, index) in filterItemList" :key="index" :class="{ 'completed': item.isChecked}"> <div class="view"> <input class="toggle" type="checkbox" v-model="item.isChecked" @click="item.isChecked=!item.isChecked"> <label>{{ item.text }}</label> <button class="destroy" @click="removeItem(index)"></button> </div> <!-- <input class="edit" value="Rule the web"> --> </li> </ul> </section> <!-- This footer should hidden by default and shown when there are todos --> <footer class="footer" v-if="keyinItemList.length"> <!-- This should be `0 items left` by default --> <span class="todo-count"><strong>{{ remaining }}</strong> item left</span> <!-- Remove this if you don't implement routing --> <ul class="filters"> <li> <a href="#/all" :class="{ selected: visibility == 'all' }">All</a> </li> <li> <a href="#/active" :class="{ selected: visibility == 'active' }">Active</a> </li> <li> <a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a> </li> </ul> <!-- Hidden if no completed items are left ↓ --> <button class="clear-completed" v-if="keyinItemList.length > remaining" @click="clearCompleted()">Clear completed</button> </footer> </section> </template> <script> import TitleComponent from './TitleComponent.vue' const filters = { all: function(todos) { return todos; }, active: function(todos) { return todos.filter((todo)=> { return !todo.isChecked; }); }, completed: function(todos) { return todos.filter((todo)=> { return todo.isChecked; }); } }; export default { name: 'TodoList', components: { TitleComponent }, data() { return { thePlaceholder: '請輸入代辦事項', keyinItemList: [], keyinItem: '', visibility: 'all' } }, computed: { filterItemList() { return filters[this.visibility](this.keyinItemList); }, remaining() { return this.keyinItemList.filter( item => { return !item.isChecked}).length; }, allDone: { get() { return this.remaining === 0; }, set(value) { this.keyinItemList.forEach((item) => { item.isChecked = value; }); } } }, mounted() { window.addEventListener("hashchange", this.onHashChange); this.onHashChange(); }, methods: { keyinTodo () { const _value = this.keyinItem && this.keyinItem.trim(); if(_value) { this.keyinItemList.push({ text: _value, isChecked: false }) this.keyinItem = null; } }, removeItem(index) { this.keyinItemList.splice(index, 1); }, clearCompleted() { this.keyinItemList = this.keyinItemList.filter( item => { return !item.isChecked}); }, onHashChange(){ const visibility = window.location.hash.replace(/#\/?/, ""); if (filters[visibility]) { this.visibility = visibility; } else { window.location.hash = ""; this.visibility = "all"; } } }, } </script> ``` --- #### 8-1 檔案: App.vue ```javascript= <template> <TodoList :createAuthor="author"></TodoList> </template> <script> import TodoList from './components/TodoList.vue' export default { name: 'App', components: { TodoList }, data() { return { author: 'Chris Chiang' } }, } </script> ``` 檔案: TodoList.vue ```javascript= <template> . . . <footer class="info"> <p>Written by {{createAuthor}}</p> </footer> </template> <script> . . components: { TitleComponent }, props: ["createAuthor"], data() { . . </script> ``` --- #### 8-2 檔案: App.vue ```javascript= <template> <TodoList :create-author="author"></TodoList> </template> ``` 檔案: TodoList.vue ```javascript= <script> . . . components: { TitleComponent }, props: { createAuthor: { type: String } }, data() { . . . </script> ``` --- #### 8-3 檔案: App.vue ```javascript= <template> <TodoList :create-author="author" @from-child="receiveMsg"></TodoList> </template> <script> . . . methods: { receiveMsg(val) { alert(val); } }, . . . </script> ``` 檔案: TodoList.vue ```javascript= <template> . . . <footer class="info"> <p @click="sendToParent()">Written by {{createAuthor}}</p> </footer> </template> <script> . . . sendToParent() { this.$emit('fromChild', 'Hello papa'); } . . . </script> ```