# 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>
```