wordpress_blog

This is a dynamic to static website.

Vue 出一個電商網站 (1)

從基礎開始,完整學習 Vue.js 開發流程。

您會學到
  • 完整了解 Vue 的運行概念
  • 學會使用 Vue 串接後端 API
  • 使用 Vue Cli (或不使用) 都可以完成一個互動網頁作品
  • 具有獨立完成一個 Vue 應用程式的能力

第1節:介紹

課程最終任務說明

本課程特色

  • 從基礎開始學
  • 含 Cli, Router, Webpack 各工具
  • ES6 常用技巧
  • 額外 API,開發電商大平台

課程說明

  • ES6
  • 使用 BABEL 編譯
  • WEBPACK
  • VUE CLI
  • SPA (Single Page Application) – 單頁應用
課程內容進程

JS+Vue.js → ES6 → WEBPACK → Vue Cli (SPA)

六角學院線上社團

第2節:基礎 Vue.js 概述

課程資源連結

附註說明

1. 為什麼要使用 Web Server
因為課程範例非常多
為了確保連結正確以及範例能夠正常運行
所以本範例一定要用 Web Server 運行

2. 一定要用 VSCode 嗎?
並沒有強制,你可以選擇自己喜歡的文字編輯器
不過記得,用其它工具時也要記得運行 Web Server 喔

3. 為什麼 VSCode Web Server 打開的路徑是錯誤的(或者沒有載入 CSS)
這部分要請先確認是否以專案形式載入 “範例資料夾”
如還有問題,可以重啟 VSCode
再不行可截圖放到問答區發問

路徑截圖

Q:為什麼會沒辦法顯示樣式呢?
這與相對路徑與絕對路徑有關係,範例中都是使用絕對路徑
所以一定要使用 Web Server 才能正確顯示喔
路徑觀念影片

Vue 開發環境介紹

Vue的開發環境
  1. 使用 Vue Dev Tools 查看本章節範例

文字編輯器:VSCode
套件:Web Server

VSCode 左方列表介紹:
  1. 檔案總管
  2. 搜尋以及取代
  3. Git 的版本控管工具
  4. Node.js 的除錯功能
  5. 套件的安裝
套件的安裝:
  • Preview on Web Server
  • VUE – vscode-vue
  • Vue 2 Snippets – Vue 2 Snippets for Visual Studio Code

Preview on Web Server 熱鍵是 Ctrl+Shift+L

Vue.js 官網文件Vue.js 中文版

兼容性 – Vue不支持IE8及以下版本。
Vue Devtools

開發版本、生產版本,差別在能不能使用開發者工具、錯誤題示完不完整。

範例上,點擊右鍵檢視原始碼,在課程中這裡面有載入一個 JavaScript vendor 裡面就包含 Vue.js,是開發的版本。

MAC 系統的 VSCode 按下 CMD + P
WINDOWS 系統的 VSCode 按下 Ctrl + P
輸入路徑:basic/dev.html

主要練習一下 Vue Dev Tools 怎麼去使用

Vue有一個很大的特色,是用資料來驅動畫面

Google – Vue.js devtools 基本介面、功能介紹

VSCode Preview on Web Server 替代套件

如果使用 Preview on Web Server 尚有任何問題,可以試著使用另一個套件 – LiveServer 。

使用方式:
  1. 安裝後重新啟動 VSCode
  2. 打開任何一個 .html 檔案
  3. 在 VSCode 畫面的右下角可以找到 Go Live 的按鈕
  4. 按下後就能打開該網頁

應用程式建立

  1. Ctrl + P
  2. 輸入路徑:basic/instant.html
  3. 所有的範例裡面,往下會有一個 “練習開始”的一個註解
建立 Vue 的應用程式
  1. 建立一個應用程式使資料呈現於畫面上
  2. 建立兩個應用程式
  3. 試試看巢狀應用程式 (不能建立巢狀應用程式)

basic/instant.html

<div id="app">
  {{ text }}
</div>
<div id="app2">
  {{ text }}
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '這裡是一段話'
  }
});
var app2 = new Vue({
  el: '#app2',
  data: {
    text: '這裡是一段話 2'
  }
});
</script>

綁定 Vue 的應用程式的時候,不是只能用 ID,也可以用 Class,然後 ID 還是比較常使用到。

Vue 一次只能綁定一個元素。

一個網頁上是可以放入兩個 Vue 的應用程式。

Vue 的應用程式是不可以使用巢狀的方式去建立,裡面這個 Vue 的應用程式是等同於無效。

我們在建立 Vue 的應用程式,其實我們通常只會建立一個,但是你要建立兩個在同一頁面上面是可行的。
特別注意的是,它是不能建立巢狀的應用程式。

  • el:element,要綁定的 DOM element
  • data:綁定的資料

雙向綁定的資料

MVVM – 維基百科
MVVM (Model – View – ViewModel) 是一種軟體架構模式。

MVVM 模式的組成部分
  • 模型:指代表內容的資料存取層 (以資料為中心)
  • 視圖:是使用者在螢幕上看到的結構、布局和外觀 (UI)。
  • 視圖模型:是暴露公共屬性和命令的視圖的抽象。有的是一個繫節氣。繫結器在視圖和資料繫節器之間進行通信。

畫面上所看到的就是 View,所操控的內容是 Model,中間自動綁定的部分就是 ViewModel。

MVVM 是什麼樣的概念
  1. 使用雙花括號與資料串接
  2. 試試看使用 HTML 屬性綁定
  3. 試試看雙向綁定
使用雙花括號、Vue指令,將資料呈現於畫面。
  • {{ message }}
  • v-model=”message”
  • v-text=”message”
  • v-html=”message”

basic/mvvm.html

<div id="app">
  // mustache語法、花括號或者是大括號
  {{ message }}
  // Vue指令 v-model="message"
  <input type="text" v-model="message">
  // Vue指令 v-text="message"
  <div v-text="message"></div>
  // Vue指令 v-html="message",可以插入新的 HTML 的標籤
  <div v-html="message"></div>
  
</div>

<script>
var app = new Vue({
  el: '#app',
  // 在此建立資料內容

  data: {
    message: '哈囉'
  }
})
</script>

MVVM 的概念

MVC架構,是一個前後端的架構。
View,就是我們看到的畫面
Controller,就是指控制器
Model,就是指模型也是資料介接的地方

MVC 示意圖
MVVM 示意圖

重點說明:VUE JS 是以資料狀態操作畫面

傳統的 jQuery,我們是直接操作畫面上的 DOM 元素。
在 Vue.js 在操作 DOM 元素的時候,是透過資料的狀態去變動它。

v-bind 動態屬性指令

指令
  1. 透過指令(v-bind)的方式,將圖片加載於畫面之上

Vue.js 學習→API→指令

v-bind:是用來更新 HTML 上面的屬性。

basic/directive.html

<div id="app">
  <img v-bind:src="imgSrc" v-bind:class="className" alt="">
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    imgSrc: 'https://images.unsplash.com/photo-1479568933336-ea01829af8de?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d9926ef56492b20aea8508ed32ec6030&auto=format&fit=crop&w=2250&q=80',
    className: 'img-fluid'
  }
})
</script>

v-for 動態產生多筆資料於畫面上

模板語法的延伸 v-if 及 v-for
  1. 使用 v-for 來呈現資料列表
  2. 使用 v-if 擷取部分資訊

basic/if_for.html

<div id="app">
  <pre>{{ list }}</pre>
  <ul>
    <li v-for="(item, index) in list" v-if="item.age < 25">
      {{ index + 1 }} - {{ item.name }} 年齡是 {{ item.age }} 歲
    </li>
  </ul>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    list: [
      {
        name: '小明',
        age: 16
      },
      {
        name: '媽媽',
        age: 38,
      },
      {
        name: '漂亮阿姨',
        age: 24
      }
    ]
  }
})
</script>
補充:Vue.js 文件

List Rendering列表渲染

講解

使用 v-for 這個指令,這個 v-for 就有點像是在 li 上面使用 forEach 一樣,那麼我們會先使用一個自定義的變數,這個自訂的變數名稱是可以自己決定的,通常會使用 item,然後 in 一個陣列上,這個陣列就會一一的將值取出來然後將它存到 item 這個變數上。

接下來我們就可以把這裡面的值,呈現在 li 上。這裡我們就可以輸入 item,那這 item 指的就是裡面這一個一個的物件,所以如果我要把這個名稱給取出來的話,我就可以使用 item.name 。

接下來我們也可以將後面的字串繼續加上去,年齡是幾歲,這裡就可以加上 item.age 。

這個就是 v-for 的一個很標準的使用方法。

如果說我需要了解這個每一個 li 它現在在陣列它的索引是第幾個,我們在這個自定義變數,我們就可以這樣寫 item index,前面這個就是陣列地值,那後面這個就是陣列的索引,那我們也可以把這個索引加到畫面上面來,index。要記得索引是從 0 開始,如果你要把它從 1 開始,那就是 index + 1。

v-if 就是在這個迴圈上面再加上一個判斷式,加上去的方式,就是可以直接 v-for 後面再加上 v-if,這個 v-if 就是直接在這個標籤上面加上一個判斷式,然後依據它所回傳的值是 true 或 false,來決定要不要去渲染這個標籤,所以當它如果是 false 的時候,這個標籤那就不會渲染,那如果它是 true 的話,就會把這一段標籤渲染出來。

接下來我們就可以加入一個判斷式叫做假設它的年齡是小於 25 歲的話,那麼就把這一個 li 給渲染出來。這個 item.age 就是指這裡面每一個人的年齡,所以我們定義在小於 25 歲的話,就把這個 li 給渲染出來。

試著使用 v-for 將這個陣列的值一一的渲染出來,那另外再使用 v-if 拿來判斷部分的條件。

使用 v-on 來操作頁面行為

處理互動式行為 v-on 指令
  1. 使用 v-on 指令來製作互動行為
  2. 反轉字串

basic/v_on.html

<div id="app">
  <input type="text" class="form-control" v-model="text">
  <button class="btn btn-primary mt-1" v-on:click="reverseText">反轉字串</button>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: ''
},
// 請在此撰寫 JavaScript
  methods: {
    reverseText: function(){
      console.log('點我', this.text);
      this.newText = this.text.split('').reverse().join('');
    }
  }  
});
</script>
介紹 v-on 以及 methods 的操作方式。
  • methods:方法

預先定義資料狀態的重要性

預先定義資料結構
  1. 了解資料結構預先定義與未定義之差異
// 接續上一個章節
// 當我們可以操作資料內容之後,預先定義資料結構變成一件很重要的事情。

// {{ newText }}
// data: {
     newText: ''
}
// 兩個資料互相綁定,在這個時候,我們一定要預先定義資料結構的習慣。

// 在我們 Vue 裡面,如果要操作資料內容,它一定要預先定義好它的資料結構。

// 在後面的課程也會介紹到,如果沒有辦法預先定義的話該怎麼做。

defined_var.html

<div id="app">
  <input type="text" class="form-control" v-model="text">
  <button class="btn btn-primary mt-1" v-on:click="reverseText">反轉字串</button>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: ''
},
// 請在此撰寫 JavaScript
  methods: {
    reverseText: function(){
      console.log('點我', this.text);
      this.newText = this.text.split('').reverse().join('');
    }
  }  
});
</script>

透過修飾符,讓 v-on 操作更簡單

處理互動式行為 v-on 指令
  1. 請將 button 改成 a 標籤,並加上 preventDefault()
  2. 使用修飾符取代
  3. 將 input 加上 Enter 事件
  4. 將範例改成使用縮寫表示

jQuery preventDefault() 寫法 – 取消默認行為

<div id="app">
  <input type="text" class="form-control" v-model="text">
  <a href="#" class="btn btn-primary mt-1">反轉字串</a>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
$(document).ready(function(){
  $('a.btn').on('click', function(evt){
    console.log(evt);
    evt.preventDefault();
  })
});
</script>

br*20

basic/modifiers.html

<div id="app">
  <input type="text" class="form-control" v-model="text">
  <a href="#" class="btn btn-primary mt-1" v-on:click.prevent="reverseText">反轉字串</a>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: ''
  },
  // 請在此撰寫 JavaScript
  methods: {
    reverseText(event){
    // console.log(event);
    // event.preventDefault();
    this.newText = this.text.split('').reverse().join('');
    }
  }
});
</script>

br*20
Vue.js 官網文件 – 事件處理
  • 事件修飾符
  • 按鍵修飾符

按鍵修飾符 – enter

<div id="app">
  <input type="text" class="form-control" v-on:keyup.enter="reverseText" v-model="text">
  <a class="btn btn-primary mt-1" v-on:click.prevent="reverseText">反轉字串</a>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: ''
  },
  // 請在此撰寫 JavaScript
  methods: {
    reverseText(event) {
      this.newText = this.text.split('').reverse().join('');
      }
    }
});
</script>

v-on 寫法

v-on:keyup.enter="reverseText"
v-on:click.prevent="reverseText"

v-on 縮寫寫法

@keyup.enter="reverseText"
@click.prevent="reverseText"

指令 v-bind

<div id="app">
  <input type="text" class="form-control" @keyup.enter="reverseText" v-model="text">
  <a v-bind:href="link" class="btn btn-primary mt-1" @click="reverseText">反轉字串</a>
  <div class="mt-3">
    {{ newText }}
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: '',
    link: 'https://www.google.com.tw/'
},
// 請在此撰寫 JavaScript
  methods: {
    reverseText(event){
      this.newText = this.text.split('').reverse().join('');
    }
  }
});  
</script>

v-bind 寫法

v-bind:href="link"

v-bind 縮寫寫法

:href="link"

縮寫

v-on == @
v-bind: == :

v-class 動態切換 className

切換 Class
  1. 為 .box 動態加上 className “rotate”

怎麼透過 Vue 來動態的切換 class name。

basic/v_class.html

// :class="{ '要加入的 ClassName': 判斷式 }"
<div id="app">
  <div class="box" :class="{ 'rotate':  isTransform }"></div>
  <hr>
  <button class="btn btn-outline-primary" @click="isTransform = !isTransform">選轉物件</button>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    isTransform: false
  },
});
</script>

<style>
.box {
  transition: transform .5s;
}
.box.rotate {
  transform: rotate(45deg)
}
</style>

computed 運算功能

計算屬性
  1. 使用 computed 取代原有的表達式
介紹 computed 這個方法。

computed 直接會將結果存到這個變數裡面,然後這個變數,是可以直接使用。
在 computed 裡面,我們所宣告任何屬性它都是 function 並且它都會 return 一個值。
類似直接在 data 上面宣告一些值,不同的是 computed 會將結果回傳於這個變數之中,並且可以運用在我們的畫面上。

basic/computed.html

<div id="app">
  <input type="text" class="form-control" v-model="text">
  <div class="mt-3">
    {{ text.split('').reverse().join('') }}
  </div>
  {{ reverseText }}
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    newText: ''
  },
  // 請在此撰寫 JavaScript
  computed: {
    reverseText: function() {
      // return 'aaa';
      return this.text.split('').reverse().join('');
    }
  }
});
</script>

注意的地方,computed 的觸發條件是透過這個 data 裡面的值,如果受到更動的時候,這個 computed 的結果,才會跟著做更動。如果說你的值並不是在這個 data 內,也就是說它不是在這個 this 下,它的更動無法被觸發的。

Methods 與 Computed 的使用情境

  • computed 是在監控資料更動後,重新運算結果呈現於畫面上。
    一般來說不會修改資料,只會回傳用於畫面呈現的資料
  • methods 就是互動的函式,需要觸發才會運作。
    會用來修改資料內容。

效能
如果資料量大,computed 自然會比較慢
只要資料變動就會觸發,無形之中執行次數也會增加
因此在大量資料時,會建議透過 methods 減少不必要的運算

  • computed:需要計算後才能使用的屬性

Vue 表單與資料的綁定

表單雙向綁定
  1. 請跟隨課程完成此章節。

介紹表單跟 Vue 的資料綁定。

basic/form.html

<div id="app">
  <h4>字串</h4>
  {{ text }}
  <input type="text" class="form-control" v-model="text">
  <hr>
  <pre>{{ textarea }}</pre>
  <textarea cols="30" rows="3" class="form-control" v-model="textarea"></textarea>
  <hr>
  <h4>Checkbox 與 Radio</h4>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="check1" v-model="checkbox1">
    <label class="form-check-label" for="check1"> 你要不要看電影 </label>
  </div>
  <hr>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="check2" v-model="checkboxArray" value="雞">
    <label class="form-check-label" for="check2">雞</label>
  </div>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="check3" v-model="checkboxArray" value="豬">
    <label class="form-check-label" for="check3">豬</label>
  </div>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="check4" v-model="checkboxArray" value="牛">
    <label class="form-check-label" for="check4">牛</label>
  </div>
  <p>晚餐火鍋裡有 <span v-for="item in checkboxArray">{{ item }} </span>。</p>
  <hr>
  <div class="form-check">
    <input type="radio" class="form-check-input" id="radio2" v-model="singleRadio" value="雞">
    <label class="form-check-label" for="radio2">雞</label>
  </div>
  <div class="form-check">
    <input type="radio" class="form-check-input" id="radio3" v-model="singleRadio" value="豬">
    <label class="form-check-label" for="radio3">豬</label>
  </div>
  <div class="form-check">
    <input type="radio" class="form-check-input" id="radio4" v-model="singleRadio" value="牛">
    <label class="form-check-label" for="radio4">牛</label>
  </div>
  <p>晚餐火鍋裡有 {{ singleRadio }}。</p>
  <hr>
  <h4>Select</h4>
  <select name="" id="" class="form-control" v-model="selected">
    <option value="" disabled>-- 請選擇 --</option>
    <option value="小明">小明</option>
    <option value="小美">漂亮的小美</option>
  </select>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '',
    textarea: '',
    checkbox1: false,
    checkboxArray: [],
    singleRadio: '',
    selected: '',
  },
});
</script>

元件基礎概念

元件化
  1. 將以下元素轉為元件

Vue 的每一個元件,它都可以獨立的儲存自己的狀態。

建立一個元件,我們可以在前方加入個 Vue 的 component,定義一個它的 component 的名稱,然後後面一樣插入一個物件。

這裡的 component 名稱是可以自己定義的,也是我們套用在畫面上所使用的標籤,這裡叫做 counter-component。

在寫 data 的時候會改使用 function,那 function 會 return 一個物件。規定一定要使用 function 來 return 這個資料內容。

如果我們是新增一個應用程式的話,則不需要使用這個 function,當我們在建立元件的時候就必須使用 function 來 return 這個資料。

接下來就是要定義,這個元件長什麼樣子,這裡是使用反引號,是 ES6 的新的語法,定義字串內容的。
(反引號多行時不需要加入其它符號,比較適合呈現本範例),

// Vue.component('component 名稱', {
     data: function() {
       return {
         物件
       }
     },
     template: `
       加入 div,並且把元件的內容直接加進來
     `
});

basic/components.html

<div id="app">
  <div>
    你已經點擊 <button class="btn btn-outline-secondary btn-sm" @click="counter += 1">{{ counter }}</button> 下。
    你已經點擊 <button class="btn btn-outline-secondary btn-sm" @click="counter += 1">{{ counter }}</button> 下。
    <counter-component></counter-component>
    <counter-component></counter-component>
    <counter-component></counter-component>
  </div>
</div>

<script>
// 請在此撰寫 JavaScript
Vue.component('counter-component',{
  data: function() {
    return {
      counter: 0
    }
  },
  template: `
    <div>
      <button class="btn btn-outline-secondary btn-sm" @click="counter += 1">{{ counter }}</button>
    </div>
  `
});


var app = new Vue({
  el: '#app',
  data: {
    counter: 0
  },
});
</script>

第3節:製作一個 Todo List 來小試身手吧

這個範例要練習什麼!?

Todo 範例製作
  1. 請跟隨課程完成此章節。

basic/todo.html – 初始程式碼

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <input type="text" class="form-control" placeholder="準備要做的任務">
    <div class="input-group-append">
      <button class="btn btn-primary" type="button">新增</button>
    </div>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link active" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" id="a1">
            <label class="form-check-label" for="a1">
              Cras justo odio
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li>
      <li class="list-group-item">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" id="a1">
            <label class="form-check-label completed" for="a1">
              Cras justo odio
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li>
      <li class="list-group-item">
        <input type="text" class="form-control">
      </li>
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {},
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

套用版型及建立待辦事項列表的資料

<input> 的內容加到 todo 的列表裡面,todo 的列表基本上會使用 v-for 來製作,會有個陣列來儲存這些資料內容,我們可以先定義它叫 item in todos,這個 todos 會用來儲存所有待辦事項資料內容。

<input> 用來新增待辦事項內容, v-model=”newTodo”,這個資料內容輸入之後,會透過<button>新增一個辦事項到 todos 裡面,<button>會加入一個方法,叫做 @click=”addTodo”。

接下來,我們把這些項目加到下面 Vue 的原始碼裡面。

在 data 裡面,我們會用 newTodo 來新增一個待辦事項,它是一個純文字,另外我們還會用 todos,它是一個陣列,用來呈現目前的待辦事項有哪些內容。

剛剛也有新增一個方法 (methods),用來新增一個新的待辦事項,叫做 addTodo,基本上整個比較基礎的架構都建立起來了。

現在因為 todos 裡面沒有內容,我們可以先試寫一份,等下要呈現的內容,等下再來新增的時候,我們心中會比較有一個概念。

我們先在 todos 新增一個假的資料,先新增一個物件,基本上它會有個 id,這個 id 的作用是,在這個地方有個 checkbox,當我們要透過這個名稱去選擇這個 checkbox 的時候,它兩邊的名稱是需要對應的,所以這個地方必須要有個 id 去選擇它,這邊會有個 id、title、completed,我們就先用這筆資料來呈現在畫面上試試看。

另外一份假的資料先註解起來,我們先留下這份要呈現的資料,在 <label> 文字內容方面,就會改成 {{ item.title }},<input>裡面的 id 會改成 :id=”item.id”,並把 <label> 裡面的 for 改成 :for=”item.id”,這樣 :for 和 :id 就可以對應起來。

completed 我們會把有沒有完成把它畫掉,它是透過 classname 做切換,現在還不能選擇,因為我們 id 是空的,id 先給它一個隨意的數字,那現在我們點擊 你好 就可以選取到這個框框的內容,它所切換的是這個 completed,在 <input> 加入 v-model=”item.completed”。

接下來透過 Vue 開發工具,來看資料是否正確。在 Root 裡面有個 todos,todos 有個第0筆資料,這裡有 completed、id、title,那我們去按下 你好 這個字,它會去觸發這個 checkbox,然後它的 completed 會從 false 變成 true,所以這個資料結構就是我們所需要的。

從上方的 <input> 去新增一個資料到下面的地方,methods 裡面的 addTodo 可以新宣告一個變數,暫時儲存 newTodo 所存下的內容,var value = this.newTodo;,接下來我們需要一個隨機的 id,id 的方式我們可以使用 timestamp 的方式,就是使用時間然後轉為數字的一種格式,我們叫做 timestamp,var timestamp = Math.floor(Date.now());,timestamp 取得可以直接使用 Date.now(); 來取得,並且透過 Math.floor 把它轉為正整數,這樣子就是一個文字以及 id,value 就會包含它的標題以及它的 timestamp。
使用 console.log(value, timestamp) 查詢這些內容,會出現標題內容、以及一串數字,數字我們就是要做為 id 使用。

接下來我們要把這筆資料寫到 todos 裡面,this.todos.push,push 就會把一個新的資料推到這個 todos 陣列裡面,推的方式我們就會新增一個物件,新的物件就會加在 todos 陣列裡面已有資料的後方,現在我們就要推一個新的物件進去,這個物件包含 id: timestamp, title: value, completed: false,然後看程式碼是否有正確運作。

<input> 新增待辦事項文字後的清除,this.newTodo = ”;,每次輸入的內容點擊新增後會加到下面的地方,並清除 <input> 文字內容。

除了按下新增按鈕新增內容以外,還有另外一個方式,是透過按鍵的行為把資料內容給新增下來,<input> 加入 @keyup.enter=”addTodo”

// 待辦事項的列表 <li> 用 v-for 製作
<li class="list-group-item" v-for="item in todos">
// <input> 用來新增待辦內容,加入 v-model="newTodo"
// <button> 透過按鈕新增待辦事項,加入 @click="addTodo"
// 把 v-for、v-model、@click 加入 Vue 原始碼裡面
data: {
  newTodo: '',
  todos: [],
},
methods: {
  addTodo: function() {

}
// 試寫一份資料內容,呈現在畫面看看
todos: [
  {
    id: '',
    title: '你好',
    completed: false
  }
]
// 把另外一份假資料註解起來,留下要呈現的。
<label> 文字內容改成 {{ item.title }}
// <input> checkbox 的 id 的部分改成 :id="item.id"
// <label> for 改成 :for="item.id"
// id 是空的,先隨意給個數字。
// <input> checkbox 加入 v-model="item.completed"
// 使用 Vue 開發者工具查看資料是否正確
// 資料結構是我們需要的
// <input> 新增資料到下方列表
methods: {
  addTodo: function() {
    var value = this.newTodo; // 儲存資料
    var timestamp = Math.floor(Date.now()); // 抓取時間命名 id
    console.log(value, timestamp);
    this.todos.push({
      id: timestamp,
      title: value,
      completed: false
    }); // 將id, title, completed加入陣列
    this.newTodo = '' // 清除輸入欄位
  }
}
// 透過按鍵行為把資料內容新增
// <input> 加入 @keyup.enter="addTodo"

basic/todo.html

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <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>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link active" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="item in todos">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">
            <label class="form-check-label" :for="item.id">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li>
      <!-- <li class="list-group-item">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" id="a1">
            <label class="form-check-label completed" for="a1">
              Cras justo odio
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li> -->
      <li class="list-group-item">
        <input type="text" class="form-control">
      </li>
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo:'',
    todos: [
      {
        id: '345',
        title: '你好',
        completed: false
      }
    ],
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo;
      var timestamp = Math.floor(Date.now());
      console.log(value, timestamp);
      this.todos.push({
        id: timestamp,
        title: value,
        computed: false
      });
      this.newTodo = '';
    }
  }
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

刪除陣列上的特定資料

// 解決新增空白的列表
// 如果value是空值,return終止
if (!value) {
  return;
}
// 解決使用空白鍵還是可以新增內容的問題
// 並且可以刪除內容前後端的空白
// 加入 .trim()
var value = this.newTodo.trim();
// 製作刪除的功能
<button> 加入 @click="removeTodo"
// methods 下方加入 removeTodo
// <li> v-for 改成 v-for="(item, key) in todos"
// <button> @click="removeTodo" 改成 @click="removeTodo(key)"
// 移除陣列的值可以使用 .splice()
removeTodo: function(key) {
  this.todos.splice(key, 1)
}

basic/todo.html

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <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>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link active" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="(item, key) in todos">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">
            <label class="form-check-label" :for="item.id">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li>
      <!-- <li class="list-group-item">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" id="a1">
            <label class="form-check-label completed" for="a1">
              Cras justo odio
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li> -->
      <li class="list-group-item">
        <input type="text" class="form-control">
      </li>
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo:'',
    todos: [
      {
        id: '345',
        title: '你好',
        completed: false
      }
    ],
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo.trim();
      var timestamp = Math.floor(Date.now());
      // console.log(value, timestamp);
      if (!value) {
        return;
      }
      this.todos.push({
        id: timestamp,
        title: value,
        computed: false
      });
      this.newTodo = '';
    },
    removeTodo: function(key) {
      this.todos.splice(key, 1);
    }
  }
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

製作頁籤分類的功能

// 製作刪除的效果、上方頁籤過濾的功能
// className 的切換
<label class="form-check-label"
:class="{'completed': item.completed}"
:for="item.id">
  {{ item.title }}
</label>
// 頁籤切換的效果
//在 data 新增一個變數 visibility
visibility: 'all'
// 刪除 class的active
// 加上 :class、@click
<ul class="nav nav-tabs card-header-tabs">
    <li class="nav-item">
      <a class="nav-link" :class="{'active': visibility == 'all'}" @click="visibility= 'all'" href="#">全部</a>
    </li>
    <li class="nav-item">
      <a class="nav-link " :class="{'active': visibility == 'active'}" @click="visibility= 'active'" href="#">進行中</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" :class="{'active': visibility == 'completed'}" @click="visibility= 'completed'" href="#">已完成</a>
    </li>
</ul>
// 過濾的功能會使用 computed
computed: {
  filteredTodos: function() {
    return this.todos;
  }
}
// <li> v-for中的 todos 換成 filteredTodos
<li class="list-group-item" v-for="(item, key) in filteredTodos">
// 使用判斷式寫一些方法
computed: {
  filteredTodos: function() {
    if (this.visibility == 'all') {
      return this.todos;
    }
    return [];
  }
}
// 加上 else if 判斷進行中、已完成
computed: {
  filteredTodos: function() {
    if (this.visibility == 'all') {
      return this.todos;
    } else if (this.visibility == 'active') {
      var newTodos = [];
      this.todos.forEach(function (item){
        if (!item.completed) {
          newTodos.push(item);
        }
      })
      return newTodos;
    } else if (this.visibility == 'completed') {
      var newTodos = [];
      this.todos.forEach(function (item){
        if (item.completed) {
          newTodos.push(item);
        }
      })
      return newTodos;
    }
    return [];
  }
}

basic/todo.html

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <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>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'all'}" @click="visibility= 'all'" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " :class="{'active': visibility == 'active'}" @click="visibility= 'active'" href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'completed'}" @click="visibility= 'completed'" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="(item, key) in filteredTodos">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">
            <label class="form-check-label" 
            :class="{'completed': item.completed}" 
            :for="item.id">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li>
      <!-- <li class="list-group-item">
        <div class="d-flex">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" id="a1">
            <label class="form-check-label completed" for="a1">
              Cras justo odio
            </label>
          </div>
          <button type="button" class="close ml-auto" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
      </li> -->
      <li class="list-group-item">
        <input type="text" class="form-control">
      </li>
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo:'',
    todos: [
      {
        id: '345',
        title: '你好',
        completed: false
      }
    ],
    visibility: 'all'
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo.trim();
      var timestamp = Math.floor(Date.now());
      // console.log(value, timestamp);
      if (!value) {
        return;
      }
      this.todos.push({
        id: timestamp,
        title: value,
        computed: false
      });
      this.newTodo = '';
    },
    removeTodo: function(key) {
      this.todos.splice(key, 1);
    }
  },
  computed: {
    filteredTodos: function() {
      if (this.visibility == 'all') {
        return this.todos;
      } else if (this.visibility == 'active') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (!item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      } else if (this.visibility == 'completed') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      }
      return [];
    }
  }
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

雙擊修改資料內容

// 編輯待辦事項
// <li> 加入 @dblclick="editTodo(item)"
// methods的地方加上 editTodo
editTodo: function(item) {
  console.log(item);
}
// data 的地方新增 cacheTodo、cacheTitle
cacheTodo: {},
cacheTitle: '',
// editTodo
editTodo: function(item) {
  console.log(item);
  this.cacheTodo = item;
  this.cacheTitle = item.title;
// 註解程式碼 <li>
// 把 <input> 取出放在 .d-flex 的下方
// 製作判斷
// .d-flex 加入 v-if="item.id !== cacheTodo.id"
// <input> 加上 v-if="item.id === cacheTodo.id"
// 打開 Vue 開發工具查看
// 把事件加到 <input> 上面
<input type="text" class="form-control"
 v-model="cacheTitle"
 @keyup.esc="cancelEdit()"
 v-if="item.id === cacheTodo.id">
// methods 下方加上 cancelEdit
cancelEdit: function() {
  this.cacheTodo = {}
}
// 將編輯的內容存進去
<input type="text" class="form-control"
 v-model="cacheTitle"
 @keyup.esc="cancelEdit()"
 @keyup.enter="doneEdit(item)"
 v-if="item.id === cacheTodo.id">
// methods 下方加上 doneEdit
doneEdit: function(item) {
  item.title = this.cacheTitle;
  this.cacheTitle = '';
  this.cacheTodo = {};
}

basic/todo.html

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <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>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'all'}" @click="visibility= 'all'" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " :class="{'active': visibility == 'active'}" @click="visibility= 'active'" href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'completed'}" @click="visibility= 'completed'" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
        <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">
            <label class="form-check-label" 
            :class="{'completed': item.completed}" 
            :for="item.id">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" @click="removeTodo(key)" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <input type="text" class="form-control"
         v-model="cacheTitle"
         @keyup.esc="cancelEdit()"
         @keyup.enter="doneEdit(item)"
         v-if="item.id === cacheTodo.id">
      </li>
      <!-- <li class="list-group-item">
        <input type="text" class="form-control">
      </li> -->
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo:'',
    todos: [
      {
        id: '345',
        title: '你好',
        completed: false
      }
    ],
    visibility: 'all',
    cacheTodo: {},
    cacheTitle: '',
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo.trim();
      var timestamp = Math.floor(Date.now());
      // console.log(value, timestamp);
      if (!value) {
        return;
      }
      this.todos.push({
        id: timestamp,
        title: value,
        computed: false
      });
      this.newTodo = '';
    },
    removeTodo: function(key) {
      this.todos.splice(key, 1);
    },
    editTodo: function(item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function() {
      this.cacheTodo = {}
    },
    doneEdit: function(item) {
      item.title = this.cacheTitle;
      this.cacheTitle = '';
      this.cacheTodo = {};
    }
  },
  computed: {
    filteredTodos: function() {
      if (this.visibility == 'all') {
        return this.todos;
      } else if (this.visibility == 'active') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (!item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      } else if (this.visibility == 'completed') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      }
      return [];
    }
  }
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

刪除項目補充說明

// 索引的位置會有變化。
// 利用之前存好的id,先宣告 newIndex 等於一個空的值,並且透過 forEach 取得的是相同的值。
// 原本傳入的是 key 改成 todo
removeTodo: function (todo) {
  var newIndex: '';

  this.todos.splice(key, 1);
},
// 刪除的部分,原本是 key 改成 item
// @click="removeTodo(item)
/* 方法一
removeTodo: function (todo) {
  var newIndex = '';
  var vm = this;
  vm.todos.forEach(function(item, key) {
    if (todo.id === item.id) {
      newIndex = key
    }
  })
  this.todos.splice(newIndex, 1);
},
*/
// 方法二
/*
removeTodo: functioon (todo) {
  var vm = this;
  var newIndex = vm.todos.findIndex(function (item, key) {
    return todo.id === item.id;
  })
  this.todos.splice(newIndex, 1);
},
*/

basic/todo.html

<div id="app">
  <div class="input-group mb-3">
    <div class="input-group-prepend">
      <span class="input-group-text" id="basic-addon1">待辦事項</span>
    </div>
    <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>
  </div>
  <div class="card text-center">
    <div class="card-header">
      <ul class="nav nav-tabs card-header-tabs">
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'all'}" @click="visibility= 'all'" href="#">全部</a>
        </li>
        <li class="nav-item">
          <a class="nav-link " :class="{'active': visibility == 'active'}" @click="visibility= 'active'" href="#">進行中</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" :class="{'active': visibility == 'completed'}" @click="visibility= 'completed'" href="#">已完成</a>
        </li>
      </ul>
    </div>
    <ul class="list-group list-group-flush text-left">
      <li class="list-group-item" v-for="(item, key) in filteredTodos" @dblclick="editTodo(item)">
        <div class="d-flex" v-if="item.id !== cacheTodo.id">
          <div class="form-check">
            <input type="checkbox" class="form-check-input" v-model="item.completed" :id="item.id">
            <label class="form-check-label" 
            :class="{'completed': item.completed}" 
            :for="item.id">
              {{ item.title }}
            </label>
          </div>
          <button type="button" class="close ml-auto" @click="removeTodo(item)" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <input type="text" class="form-control"
         v-model="cacheTitle"
         @keyup.esc="cancelEdit()"
         @keyup.enter="doneEdit(item)"
         v-if="item.id === cacheTodo.id">
      </li>
      <!-- <li class="list-group-item">
        <input type="text" class="form-control">
      </li> -->
    </ul>
    <div class="card-footer d-flex justify-content-between">
      <span>還有 3 筆任務未完成</span>
      <a href="#">清除所有任務</a>
    </div>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    newTodo:'',
    todos: [
      // {
      //   id: '345',
      //   title: '你好',
      //   completed: false
      // }
    ],
    visibility: 'all',
    cacheTodo: {},
    cacheTitle: '',
  },
  methods: {
    addTodo: function() {
      var value = this.newTodo.trim();
      var timestamp = Math.floor(Date.now());
      // console.log(value, timestamp);
      if (!value) {
        return;
      }
      this.todos.push({
        id: timestamp,
        title: value,
        computed: false
      });
      this.newTodo = '';
    },
    // 方法一
    // removeTodo: function(todo) {
    //   var newIndex = '';
    //   var vm = this;
    //   vm.todos.forEach(function(item, key) {
    //     if (todo.id === item.id) {
    //       newIndex = key
    //     }
    //   })
    //   this.todos.splice(newIndex, 1);
    // },
    // 方法二
    removeTodo: function (todo) {
      var vm = this;
      var newIndex = vm.todos.findIndex(function (item, key) {
        return todo.id === item.id;
      })
      this.todos.splice(newIndex, 1);
    },
    editTodo: function(item) {
      console.log(item);
      this.cacheTodo = item;
      this.cacheTitle = item.title;
    },
    cancelEdit: function() {
      this.cacheTodo = {}
    },
    doneEdit: function(item) {
      item.title = this.cacheTitle;
      this.cacheTitle = '';
      this.cacheTodo = {};
    }
  },
  computed: {
    filteredTodos: function() {
      if (this.visibility == 'all') {
        return this.todos;
      } else if (this.visibility == 'active') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (!item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      } else if (this.visibility == 'completed') {
        var newTodos = [];
        this.todos.forEach(function(item) {
          if (item.completed) {
            newTodos.push(item);
          }
        })
        return newTodos;
      }
      return [];
    }
  }
});
</script>

<style>
.completed {
  text-decoration: line-through
}
</style>

換你來試試看,完成這份作業吧

顯示還剩下多少未完成項目

HTML

<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;
  }
}
刪除所有資料

HTML

<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 = [];
  }
}

參考資源:Alysa ChanAlysa Chan筆記

作業連結:Todo Vue.js 作業

實戰體驗 – Todo List 的資源連結

作業模板:

Todo Demo Template
“進入此連結後可 Fork 到自己的帳號下”
此模板與課程範例進行的類似
同學可以在此練習作業
並透過 Udemy 問答區繳交作業

透過此範例,同學可提早完整了解 Vue.js 的概觀
接下來在學習 Vue 的語法細節及生命週期
會更容易了解這些的相關性

第4節:進階模板語法介紹

Vue.js 模板語法

模板資料細節說明

基礎模板語法
  • 字串
  • 原始 HTML
  • 單次綁定
  • 表達式
  • HTML 屬性

v-html – 額外注意事項

在網站上動態渲染任意HTML是非常危險的,因為容易導致XSS攻擊。只在可信內容上使用v-html永不用在用戶提交的內容上。

跨網站指令碼 (Cross-site scripting,通常簡稱為:XSS),是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。它允許惡意使用者將程式碼注入到網頁上,其他使用者在觀看網頁時就會受到影響。

template/basic.html

<div id="app">
  <h4>字串</h4>
  {{ text }}
  <input type="text" class="form-control" v-model="text">

  <h4 class="mt-3">原始 HTML</h4>
  {{ rawHtml }}
  <p v-html="rawHtml">請在此加入原始 HTML 結構</p>
  <p><a href="https://cn.vuejs.org/v2/api/#v-html">額外注意事項</a></p>

  <h4 class="mt-3">單次綁定</h4>
  <div v-text="text" v-once>請將此欄位改為單次綁定</div>

  <h4 class="mt-3">表達式</h4>
  <p>練習不同的表達式</p>
  {{ text + rawHtml }}
  {{ text.split('').reverse().join('') }}
  {{ number1 * number2 }}

  <hr>
  <h4 class="mt-3">HTML 屬性</h4>
  <p :id="htmlId">請綁定上 ID</p>
  
  <input type="text" :disabled="isDisabled" class="form-control" placeholder="請在此加上動態 disabled">
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    text: '這是一段文字',
    rawHtml: `<span class="text-danger">紅色文字</span>`,
    number1: 100,
    number2: 300,
    htmlId: 'HTMLID',
    isDisabled: true
  },
});
</script>

動態切換 ClassName 及 Style 多種方法

切換樣式
  • 物件寫法
  • 物件寫法 2
  • 陣列寫法
  • 綁定行內樣式
  • 自動加上 Prefix (每個版本結果不同)
介紹怎麼切換樣式。

實際在做的時候,資料狀態建議自己輸入一次,才能比較了解切換狀態的時候,資料邏輯是怎麼去建構的。

不能使用 – 這個符號(Ex:bg-danger),來做為物件選取的名稱,會在 Console 跳出錯誤,雖然可以操作,但不是正確的寫法,正確寫法是改成用中括號的方式來選取 (Ex:[‘bg-danger’])。

CSS屬性 user-select 控制用戶是否可用文本。

template/v_class.html

<div id="app">
  <h4>物件寫法</h4>
  <div class="box" :class="{'rotate': isTransform, 'bg-danger': boxColor}"></div>
  <p>請為此元素加上動態 className</p>
  <hr>
  <button class="btn btn-outline-primary" v-on:click="isTransform = !isTransform">選轉物件</button>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="classToggle1" v-model="boxColor">
    <label class="form-check-label" for="classToggle1">切換色彩</label>
  </div>
  <hr>
  <h5>物件寫法 2</h5>
  <div class="box" :class="objectClass"></div>
  <p>請將此範例改為 "物件" 寫法</p>
  <hr>
  <button class="btn btn-outline-primary" @click="objectClass.rotate = !objectClass.rotate">選轉物件</button>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="classToggle2" v-model="objectClass['bg-danger']">
    <label class="form-check-label" for="classToggle2">切換色彩</label>
  </div>
  <hr>
  <h4>陣列寫法</h4>
  <button class="btn" :class="['btn-outline-primary','active']">請操作本元件</button>
  <button class="btn" :class="arrayClass">請操作本元件</button>
  <p>請用陣列呈現此元件 className</p>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="classToggle3" v-model="arrayClass" value="btn-outline-primary">
    <label class="form-check-label" for="classToggle3">切換樣式</label>
  </div>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="classToggle4" v-model="arrayClass" value="active">
    <label class="form-check-label" for="classToggle4">啟用元素狀態</label>
  </div>
  <hr>
  <h4>綁定行內樣式</h4>
  <p>請用不同方式綁定以下行內樣式</p>
  <div class="box" :style="{backgroundColor: 'red'}"></div>
  <div class="box" :style="styleObject"></div>
  <div class="box" :style="[{backgroundColor: 'red', borderWidth: '5px'}]"></div>
  <div class="box" :style="[styleObject, styleObject2]"></div>
  <hr>
  <h5>自動加上 Prefix (每個版本結果不同)</h5>
  <div class="box" :style="styleObject3"></div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    isTransform: false,
    boxColor: false,
    objectClass: {
      'rotate': false,
      'bg-danger': false,
    },

    // Array 操作
    arrayClass: [],

    // 行內樣式
    // 使用駝峰式命名
    styleObject: {
      backgroundColor: 'red',
      borderWidth: '5px'
    },
    styleObject2: {
      boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
    },
    styleObject3: {
      userSelect: 'none'
    }
  },
});
</script>

<style>
.box {
  transition: all .5s;
}
.box.rotate {
  transform: rotate(45deg)
}
</style>

v-for 與其使用細節

v-for 迴圈
  • 陣列與物件的迴圈
  • Key
  • Filter
  • 不能運作的狀況
  • 純數字的迴圈
  • Template 的運用
  • v-for 與 v-if
  • v-for 與 元件

Vue.js Vue.set

注意:現在建議元件使用 v-for 都加上 key。 參考

template/v_for.html

<div id="app">
  <h4>陣列與物件的迴圈</h4>
  <p>請使用 v-for 在陣列與物件上,並且加上索引</p>
  <ul>
    <li v-for="(item, key) in objectData">
      {{ key }} - {{ item.name }} {{ item.age }} 歲
    </li>
  </ul>
  <ul>
    <li v-for="(item, key) in objectData">
      {{ key }} - {{ item.name }} {{ item.age }} 歲
    </li>
  </ul>
  <hr>
  <h4>Key</h4>
  <p>請在範例上補上 key,並觀察其差異</p>
  <ul>
    <li v-for="(item, key) in arrayData" :key="item.age">
      {{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
    </li>
  </ul>
  <button class="btn btn-outline-primary" @click="reverseArray">反轉陣列</button>

  <h4>Filter</h4>
  <p>請製作過濾資料</p>
  <input type="text" class="form-control" v-model="filterText" @keyup.enter="filterData">
  <ul>
    <li v-for="(item, key) in filterArray" :key="item.age">
      {{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
    </li>
  </ul>

  <h4>不能運作的狀況</h4>
  <p>講師說明</p>
  <button class="btn btn-outline-primary" @click="cantWork">無法運行</button>
  <ul>
    <li v-for="(item, key) in arrayData" :key="item.age">
      {{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
    </li>
  </ul>

  <h4>純數字的迴圈</h4>
  <ul>
    <li v-for="item in 10">
      {{ item }}
    </li>
  </ul>

  <h4>Template 的運用</h4>
  <p>請將兩個 tr 一組使用 v-for</p>
  <table class="table">
    <template v-for="item in arrayData">
      <tr>
        <td>{{item.age}}</td>
      </tr>
      <tr>
        <td>{{item.name}}</td>
      </tr>
    </template>
  </table>

  <h4>v-for 與 v-if</h4>
  <p>請加上 v-if 判斷式</p>
  <ul>
    <li v-for="(item, key) in arrayData" v-if="item.age <= 19">
      {{ key }} - {{ item.name }} {{ item.age }} 歲
    </li>
  </ul>

  <h4>v-for 與 元件</h4>
  <p>講師說明</p>
  <ul>
    <list-item :item="item" v-for="(item, key) in arrayData" :key="item.age"></list-item>
  </ul>
  <p>注意:現在建議元件使用 v-for 都加上 key。<a href="https://cn.vuejs.org/v2/guide/list.html#%E4%B8%80%E4%B8%AA%E7%BB%84%E4%BB%B6%E7%9A%84-v-for">參考</a></p>
</div>

<script>
Vue.component('list-item', {
  template: `
    <li>
      {{ item.name }} {{ item.age }} 歲
    </li>
  `,
  props: ['item']
});

var app = new Vue({
  el: '#app',
  data: {
    arrayData: [
      {
        name: '小明',
        age: 16
      },
      {
        name: '漂亮阿姨',
        age: 24
      },
      {
        name: '杰倫',
        age: 20
      }
    ],
    objectData: {
      ming: {
        name: '小明',
        age: 16
      },
      auntie: {
        name: '漂亮阿姨',
        age: 24
      },
      jay: {
        name: '杰倫',
        age: 20
      }
    },
    filterArray: [],
    filterText: ''
  },
  methods: {
    // 請在此練習 JavaScript
    reverseArray: function () {
      this.arrayData.reverse()
      console.log(this.arrayData)
    },
    filterData: function() {
      var vm = this; // (this)
      vm.filterArray = vm.arrayData.filter(function(item) {
        console.log(vm.filterText, item.name, item.name.match(vm.filterText));
        return item.name.match(vm.filterText);
      });

    },
    cantWork: function() {
      // 無法運行一
      // this.arrayData.length = 0;
      // console.log(this.arrayData);

      // 無法運行二
      // this.arrayData[0] = {
      //   name: '小強',
      //   age: 99,
      // }
      // console.log(this.arrayData);

      // 解法
      Vue.set(this.arrayData, 0, {
        name: '小強',
        age: 99,
      })
    }
  }
});
</script>

Vue.set

v-if 與其使用細節

v-if 模板判斷
  • v-if, v-else
  • template 標籤
  • v-else-if
  • KEY
  • v-if 與 v-show

Conditional Rendering條件渲染 v-if

v-if, v-else – 成功、失敗範例

// 寫法一
// 成功
v-if="isSuccess == true"
// 失敗
v-if="isSuccess == false"
// 寫法二
// 成功
v-if="isSuccess"
// 失敗
v-if="!isSuccess"
// 寫法三
// 成功
v-if="isSuccess"
// 失敗
v-else

template/v_if.html

<div id="app">
  <h4>v-if, v-else</h4>
  <p>使用 v-if, v-else 切換物件呈現</p>
  <div class="alert alert-success" v-if="isSuccess">成功!</div>
  <div class="alert alert-danger" v-else>失敗!</div>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="isSuccess" v-model="isSuccess">
    <label class="form-check-label" for="isSuccess">啟用元素狀態</label>
  </div>

  <h4>template 標籤</h4>
  <p>使用 template 切換多數 DOM 呈現</p>
  <table class="table">
    <thead>
      <th>編號</th>
      <th>姓名</th>
    </thead>
    <template v-if="showTemplate">
      <tr>
        <td>1</td>
        <td>安妮</td>
      </tr>
      <tr>
        <td>2</td>
        <td>小明</td>
      </tr>
    </template>
  </table>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="showTemplate" v-model="showTemplate">
    <label class="form-check-label" for="showTemplate">啟用元素狀態</label>
  </div>
  <hr>
  <h4>v-else-if</h4>
  <p>使用 v-else-if 做出分頁頁籤</p>
  <ul class="nav nav-tabs">
    <li class="nav-item">
      <a class="nav-link" href="#" :class="{'active': link == 'a'}" @click.prevent="link = 'a'">標題一</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#" :class="{'active': link == 'b'}" @click.prevent="link = 'b'">標題二</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#" :class="{'active': link == 'c'}" @click.prevent="link = 'c'">標題三</a>
    </li>
  </ul>
  <div class="content">
    <div v-if="link == 'a'">A</div>
    <div v-else-if="link == 'b'">B</div>
    <div v-else-if="link == 'c'">C</div>
  </div>
  <hr>
  <h4>KEY</h4>
  <p>講師說明 Vue 切換狀態可能遇到的問題</p>
  <template v-if="loginType === 'username'">
    <label>Username</label>
    <input class="form-control" placeholder="Enter your username":key="1">
  </template>
  <template v-else>
    <label>Email</label>
    <input class="form-control" placeholder="Enter your email address" :key="2">
  </template>
  <button class="btn btn-outline-primary mt-3" @click="toggleLoginType">切換狀態</button>
  <hr>
  <h4>v-if 與 v-show</h4>
  <p>講師說明 v-if 與 v-show 的差異</p>
  <div class="alert alert-success" v-if="isSuccess">成功!</div>
  <div class="alert alert-danger" v-if="!isSuccess">失敗!</div>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="isSuccess2" v-model="isSuccess">
    <label class="form-check-label" for="isSuccess2">啟用元素狀態</label>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    isSuccess: true,
    showTemplate: true,

    link: 'a',

    loginType: 'username'
  },
  methods: {
    toggleLoginType: function () {
      return this.loginType = this.loginType === 'username' ? 'email' : 'username'
    }
  }
});
</script>

特別記得的是 key 這個指令,可能是在使用 Vue 常踩到的地雷。

Computed 與 Watch

計算及監聽
  • Computed
  • Watch

Computed Properties and Watchers計算屬性和偵聽器

template/computed_watch.html

<div id="app">
  <h4>Computed</h4>
  <p>使用 Computed 來過濾資料。</p>
  <input type="text" class="form-control" v-model="filterText">
  <ul>
    <li v-for="(item, key) in filterArray" :key="item.age">
      {{ key }} - {{ item.name }} {{ item.age }} 歲 <input type="text">
    </li>
  </ul> 
  <p>使用 Computed 來呈現時間格式。</p>
  <p>{{ formatTime }}</p>
  <h4>Watch</h4>
  <p>使用 trigger 來觸發旋轉 box、並在三秒後改變回來</p>
  <div class="box" :class="{'rotate': trigger }"></div>
  <hr>
  <button class="btn btn-outline-primary" @click="trigger = true">Counter</button>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    arrayData: [
      {
        name: '小明',
        age: 16
      },
      {
        name: '漂亮阿姨',
        age: 24
      },
      {
        name: '杰倫',
        age: 20
      }
    ],
    filterText: '',
    trigger: false,
    newDate: 0
  },
  computed: {
    filterArray: function() {
      var vm = this;
      return vm.arrayData.filter(function(item) {
        return item.name.match(vm.filterText);
      })
    },
    formatTime: function () {
      console.log(this.newDate);
      var dates = new Date(this.newDate * 1000);
      var year = dates.getFullYear();
      var month = dates.getMonth() + 1;
      var date = dates.getDate();
      var hours = dates.getHours();
      var minutes = dates.getMinutes();
      var seconds = dates.getSeconds();
      return `${year}/${month}/${date} ${hours}:${minutes}:${seconds}`
    }
  },
  watch: {
    trigger: function () {
      var vm = this;
      setTimeout(() => {
        vm.trigger = false
      }, 3000);
    }
  },
  mounted:function () {
    this.newDate = Math.floor(Date.now() / 1000);
  }
});
</script>

<style>
.box {
  transition: all .5s;
}
.box.rotate {
  transform: rotate(45deg)
}
</style>

表單細節操作

template/form.html

<div id="app">
  <h4>Select</h4>
  <select name="" id="" class="form-control" v-model="selected">
    <option disabled value="">請選擇</option>
    <option value="小美">小美</option>
    <option value="可愛小妞">可愛小妞</option>
    <option value="漂亮阿姨">漂亮阿姨</option>
  </select>
  <p>小明喜歡的女生是 {{ selected }}。</p>
  <hr>
  <select name="" id="" class="form-control" v-model="selected2">
    <option disabled value="">請選擇</option>
    <option :value="item" v-for="item in selectData">{{ item }}</option>
  </select>
  <p>小明喜歡的女生是 {{ selected2 }}。</p>
  <hr>
  <h4 class="mt-3">多選</h4>
  <select name="" id="" class="form-control" multiple v-model="multiSelected">
    <option value="小美">小美</option>
    <option value="可愛小妞">可愛小妞</option>
    <option value="漂亮阿姨">漂亮阿姨</option>
  </select>
  <p>小明喜歡的女生是 <span v-for="item in multiSelected">{{ item }} </span>。</p>
  <hr>
  <h4 class="mt-3">複選框</h4>
  <div class="form-check">
    <input type="checkbox" class="form-check-input" id="sex" v-model="sex" true-value="男生" false-value="女生">
    <label class="form-check-label" for="sex">{{ sex }}</label>
  </div>
  <h4 class="mt-3">修飾符</h4>
  {{ lazyMsg }}
  <input type="text" class="form-control" v-model.lazy="lazyMsg">
  <br>
  <pre>{{ typeof(age) }} {{ age }}</pre>
  <input type="number" class="form-control" v-model.number="age">
  <br>
  {{ trimMsg }}緊黏的文字
  <input type="text" class="form-control" v-model.trim="trimMsg">
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    singleRadio: '',
    selected: '',
    selectData: ['小美', '可愛小妞', '漂亮阿姨'],
    selected2: '',
    multiSelected: [],
    sex: '男生',

    // 修飾符
    lazyMsg: '',
    age: '',
    trimMsg: ''
  },
});
</script>

補充:可透過 shift 或 cmd 來多選 Windows 則是 shift 或 ctrl

v-on 的頁面操作細節

事件修飾符
  • .stop – 調用 event.stopPropagation(),停止由內而外。
  • .prevent – 調用 event.preventDefault(),停止事件的默認動作。
  • .capture – 添加事件偵聽器時使用 capture 模式,由內而外改成由外而內。
  • .self – 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調,只會觸發自己這個元素。
  • .once – 指觸發一次回調,事件只觸發一次。
按鍵修飾符
  • .{keyCode | keyAlias} – 只當事件是從特定鍵觸發時才觸發回調。
  • 別名修飾 – .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right
  • 修飾符來實現僅在按下相應按鍵時才觸發鼠標或按鍵事件的監聽器 – .ctrl, .alt, .shift, .meta

template/v_on.html

<div id="app">
  <p>請切換下方 box 的 className</p>
  <div class="box" :class="{'rotate': isRotate }"></div>
  <hr>
  <button class="btn btn-outline-primary" @click="changeRotate">切換 box 樣式</button>
  <hr>
  <h4>帶入參數</h4>
  <ul>
    <li v-for="item in arrayData" class="my-2">
      {{ item.name }} 有 {{ item.cash }} 元 
      <button class="btn btn-sm btn-outline-primary" @click="storeMoney(item)">儲值</button>
    </li>
  </ul>
  <h4>修飾符</h4>
  <h5>事件修飾符</h5>
  <ul>
    <li>.stop - 調用 event.stopPropagation()。</li>
    <li>.prevent - 調用 event.preventDefault()。</li>
    <li>.capture - 添加事件偵聽器時使用 capture 模式。</li>
    <li>.self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。</li>
    <li>.once - 只觸發一次回調。</li>
  </ul>
  <h6>將此範例加上 stopPropagation</h6>
  <div class="p-3 bg-primary" @click.stop="trigger('div')">
    <span class="box" @click.stop="trigger('box')"></span>
  </div>

  <h6 class="mt-3">事件偵聽器時使用 capture 模式</h6>
  <div class="p-3 bg-primary" @click.capture="trigger('div')">
    <span class="box d-flex align-items-center justify-content-center" @click.capture="trigger('box')">
      <button class="btn btn-outline-secondary" @click.capture="trigger('button')">按我</button>
    </span>
  </div>

  <h6 class="mt-3">事件偵聽器時使用 self 模式</h6>
  <div class="p-3 bg-primary" @click.self="trigger('div')">
    <span class="box d-flex align-items-center justify-content-center" @click.self="trigger('box')">
      <button class="btn btn-outline-secondary" @click.self="trigger('button')">按我</button>
    </span>
  </div>

  <h6 class="mt-3">事件偵聽器只觸發一次</h6>
  <div class="p-3 bg-primary" @click.once="trigger('div')">
    <span class="box d-flex align-items-center justify-content-center" @click.once="trigger('box')">
      <button class="btn btn-outline-secondary" @click.once="trigger('button')">按我</button>
    </span>
  </div>

  <h5>按鍵修飾符</h5>
  <ul>
    <li>.{keyCode | keyAlias} - 只當事件是從特定鍵觸發時才觸發回調。</li>
    <li>別名修飾 - .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right</li>
    <li>修飾符來實現僅在按下相應按鍵時才觸發鼠標或鍵盤事件的監聽器 - .ctrl, .alt, .shift, .meta</li>
  </ul>
  <h6 class="mt-3">keyCode</h6>
  <input type="text" class="form-control" v-model="text" @keyup.13="trigger(13)">

  <h6 class="mt-3">別名修飾</h6>
  <input type="text" class="form-control" v-model="text" @keyup.space="trigger('space')">

  <h6 class="mt-3">相應按鍵時才觸發的監聽器</h6>
  <input type="text" class="form-control" v-model="text" @keyup.shift.enter="trigger('shift + Enter')">
  <h5>滑鼠修飾符</h5>
  <ul>
    <li>.left - (2.2.0) 只當點擊鼠標左鍵時觸發。</li>
    <li>.right - (2.2.0) 只當點擊鼠標右鍵時觸發。</li>
    <li>.middle - (2.2.0) 只當點擊鼠標中鍵時觸發。</li>
  </ul>
  <h6 class="mt-3">滑鼠修飾符</h6>
  <div class="p-3 bg-primary">
    <span class="box" @click.right="trigger('Right button')">
    </span>
  </div>
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    arrayData: [
      {
        name: '小明',
        age: 16,
        cash: 500
      },
      {
        name: '漂亮阿姨',
        age: 24,
        cash: 1000
      },
      {
        name: '杰倫',
        age: 20,
        cash: 5000
      }
    ],
    isRotate: false,
    text: ''
  },
  methods: {
    changeRotate: function() {
      this.isRotate = !this.isRotate;
    },
    storeMoney: function(item) {
      item.cash = item.cash + 500;
    },
    trigger: function(name) {
      console.log(name, '此事件被觸發了')
    }
  }
  // 解答
});
</script>

<style>
.box {
  display: block;
  transition: all .5s;
}
.box.rotate {
  transform: rotate(45deg)
}
</style>

Template 章節作業說明

作業練習:表格排序

模板練習作業:透過點擊 th 方式,反轉表格的排序

僅需要排序價格、到期日。

提示:
  1. 將資料排序改為使用 computed 輸出
  2. 使用迴圈的方式,重新依據點擊排序內容,並透過 computed 輸出
  3. 反轉時,th 指標需給與正確方向
  4. 加分題:第二次點擊時再次反轉資料

章節作業模板

如果課程中沒有看到章節作業模板
煩請重新下載課程範例

template/homework_sortby.html

<div id="app" class="app">
  <table class="table">
    <thead>
      <tr>
        <th>品名</th>
        <th class="click" :class="{'active': link == 'linkPrice'}"
        @click="sortActive = 'price'; isReverse = !isReverse; link='linkPrice'">價格 
          <!-- isReverse 為反轉 className -->
          <span class="icon isReverse" :class="{'inverse': isReverse}"
            v-if="sortActive === 'price'">
            <i class="fas fa-angle-up text-success"></i>
          </span>
        </th>
        <th class="click" :class="{'active': link == 'linkExpiryDate'}"
        @click="sortActive = 'expiryDate'; isReverse = !isReverse; link='linkExpiryDate'">到期日 
          <span class="icon isReverse" :class="{'inverse': isReverse}"
            v-if="sortActive === 'expiryDate'">
            <i class="fas fa-angle-up text-success"></i>
          </span>
        </th>
      </tr>
      <tr v-for="(item, key) in sortArray" :key="item.name"  v-if="link == ''">
        <td>{{ item.name }}</td>
        <td>{{ item.price }}</td>
        <td>{{ item.expiryDate }}</td>
      </tr>
      <tr v-for="(item, key) in sortArray" :key="item.name"  v-if="link == 'linkPrice'">
        <td>{{ item.name }}</td>
        <td>{{ item.price }}</td>
        <td>{{ item.expiryDate }}</td>
      </tr>
      <tr v-for="(item, key) in sortArray" :key="item.name"  v-if="link == 'linkExpiryDate'">
        <td>{{ item.name }}</td>
        <td>{{ item.price }}</td>
        <td>{{ item.expiryDate }}</td>
      </tr>
    </thead>
  </table>
</div>

<script>
// 參考語法
// // 使用 Sort 排序資料
// data = data.sort(function (a, b) {
//   // 抓出排序資料的值
//   a = a[欄位]
//   b = b[欄位]

//   // 回傳 1, 0, -1 來進行排列
//   // 詳細規則可參考 https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
//   return (a === b ? 0 : a > b ? 1 : -1) * 正反排序數值
// })

var app = new Vue({
  el: '#app',
  data: {
    productData: [
      {
        name: '巧呼呼蘇打水',
        price: 30,
        expiryDate: 90
      },
      {
        name: '心驚膽跳羊肉飯',
        price: 65,
        expiryDate: 2
      },
      {
        name: '郭師傅武功麵包',
        price: 32,
        expiryDate: 1
      },
      {
        name: '不太會過期的新鮮牛奶',
        price: 75,
        expiryDate: 600
      },
      {
        name: '金殺 巧粒粒',
        price: 120,
        expiryDate: 200
      }
    ],
    isReverse: true,
    sortActive: '',
    link: ''
  },
  // 請在此撰寫 JavaScript
  computed: {
    sortArray: function() {
      var vm = this;
      // var sortItem = vm.sortActive;
      // 使用 sort 排序資料
      vm.productData = this.productData.sort(function(a, b) {
        // 抓出排序資料的值
        a = a[vm.sortActive];
        b = b[vm.sortActive];
        // 條件(三元)運算式
        // return a < b ? -1 : a > b ? 1 : 0;
        // 相當於
        if(a<b) { return -1; }
        else if (a>b) { return 1; }
        else { return 0; }
      });
      if(vm.isReverse) {
        return vm.productData;
      } else {
        return vm.productData.reverse();
      }
    }
  }
});
</script>

<style>
.app {
  max-width: 1140px;
  margin: 10px auto;
}

.table th.click {
  cursor: pointer;
}

.table th.click:hover {
  color: #00ffa2;
}

.icon {
  display: inline-block;
}

.icon.inverse {
  transform: rotate(180deg)
}

.active {
  color: #28a745;
}
</style>

Array.prototype.sort()條件運算子

// 比較函式會是以下形式:
function compare(a, b) {
  if (在某排序標準下 a 小於 b) {
    return -1;
  }
  if (在某排序標準下 a 大於 b) {
    return 1;
  }
  // a 必須等於 b
  return 0;
}

第5節:Vue 的生命週期

實際運行範例 (實例生命週期鉤子)

了解生命週期,對於熟悉 Vue.js 有很大的幫助。

延伸介紹 – Vue 生命週期
  1. 觀察生命週期
  2. 加上 keep-alive 讓元件資料狀態不被移除

basic/v_lifecycle.html

<div id="app" class="text-center">
  <h3>Let's check out the lifecycle of this hur' child.</h3>
  <h4>Check the console!</h4>
  <button @click="toggleShow" class="btn btn-primary">
    <span v-if="isShowing">Hide child</span>
    <span v-else>Show child</span>
  </button>
  <hr>
  <keep-alive>
    <app-child v-if="isShowing"></app-child>
  </keep-alive>
</div>

<script type="text/x-template" id="childarea">
  <div>
    <h4>Hello! {{ text }}</h4>
    <input type="text" class="form-control" v-model="text">
  </div>
</script>

<script>
//*** 重要 ***///
const Child = {
  template: '#childarea',
  data: function () {
    return {
      text: 'Vue data 資料狀態'
    }
  },
  beforeCreate() {
    console.log(`beforeCreate! ${this.text}`);
  }, 
  created() {
    alert(`created! ${this.text}`);
  }, 
  beforeMount() {
    alert(`beforeMount! ${this.text}`);
  }, 
  mounted() {
    alert(`mounted! ${this.text}`);
  },
  updated () {
    console.log(`updated! ${this.text}`);
  },
  activated () {
    alert(`activated! ${this.text}`);
  },
  deactivated () {
    alert(`deactivated! ${this.text}`);
  },
  beforeDestroy() {
    console.log(`beforeDestroy! ${this.text}`);
  }, 
  destroyed() {
    console.log(`destroyed! ${this.text}`);
  }
};

new Vue({
  el: '#app',
  data() {
    return {
      isShowing: false 
    }
  },
  methods: {
    toggleShow() {
      this.isShowing = !this.isShowing;
    }
  },
  components: {
    appChild: Child
  }
});
</script>

Instance Lifecycle Hooks實例生命週期鉤子

Vue 生命週期的簡報說明

Vue 生命週期的簡報說明
章節重點說明
  1. 如果要使用 AJAX 至少要到 CREATED 才能用
  2. 想要維持資料狀態,可以使用 <keep-alive>