iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 15
0
Modern Web

實作小範例入門 Vue & Vuex 2.0系列 第 15

vue & vuex 15 - 經典 todo list - III (update)

今天目標:

  1. 希望可以更改 todo 內容。

覺得 update 算是 todo 裡面比較複雜的地方。

因為需要切換成有輸入框的修改模式

todo.vue

<template>
  <div class="container">
    <h1>vue & vuex Todo List example</h1>
    <hr>
    <div class="row">
      <div class="input-group col-md-4">
        <input
          type="text"
          class="form-control"
          placeholder="add Todo.."
          v-model="newTodo"
          @keyup.enter="actionAddTodo" />
        <span class="input-group-btn">
          <button class="btn btn-success" type="button" @click="actionAddTodo">
            <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
          </button>
        </span>
      </div>
    </div>
    <!-- 左右兩個欄位分別存放 todo / done -->
    <div class="row">
      <div class="col-md-6">
        <h2>Todo List:</h2>
        <ol>
          
		  <todoItem v-for="(item, index) in todoList" :item="item" />
		  
        </ol>
      </div>
      <div class="col-md-6">
        <h2>Done List:</h2>
        <ol >
          <li v-for="(item, index) in doneList">
            <label>
              <input 
                type="checkbox"
                :checked="item.done"
                @change="toggleTodo( item.key )">
                {{ item.content }}
            </label>
          </li>
        </ol>
      </div>
    </div><!-- end row -->
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';
import todoItem from '../components/todo-item.vue';

export default {
  components: {
    todoItem
  },
  data () {
    return {
      newTodo: ''
    }
  },
  computed: mapGetters({
    todoList: 'getTodo',
    doneList: 'getDone',
  }),
  methods: {
    ...mapActions([
        'toggleTodo',
        'deleteTodo',
    ]),
    actionAddTodo () {
      this.$store.dispatch('addTodo', this.newTodo);
      this.newTodo = '';
    }
  }
}
</script>

為什麼要包裝 todoItem ?

因為需要切換兩種狀態,因此需要有紀錄這個數據的地方。

可是這個數據不太需要紀錄在 vuex 裡面,

也不可能紀錄在 page 的 data 裡面。

有 100 todo 怎麼辦?

所以包裝成一個 component 利用每個封閉的 data 獨自去紀錄,

而 component 本身只需要接收父層傳遞 todo object 單一資料既可。


todoItem.vue

<template>
  <li>
    
	<div v-if="!updateMode">
      <label>
        <input 
          type="checkbox"
          :checked="item.done"
          @change="toggleTodo( item.key )">
          {{ item.content }}
      </label>
      <!-- 新增切換修改按鈕 -->
      <button class="btn btn-xs btn-primary" @click="showEditMode">
        <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
      </button>
      <button class="btn btn-xs btn-danger" @click="deleteTodo( item.key )">
        <span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
      </button>
    </div>
	
    <div v-if="updateMode" >
      <input
        class="edit-input"
        v-focus="updateMode"
        placeholder="edit Todo.."
        :value="item.content"
        @keyup.enter="actionEdit"
        @blur="cancelEdit"
        @keyup.esc="cancelEdit"/>
    </div>
	
  </li>
</template>

<script>
import { mapGetters, mapActions } from 'vuex';

export default {
  props: {
    item: Object
  },
  data () {
    return {
      updateMode: false // 修改模式預設值
    }
  },
  /*
    此 directives 是參考作者的 code
    主要功能是切換模式之後 focus input 功能
    value 為 updateMode
    $nextTick 功能是:上個動作完成後,才執行裡面包含的程式。
    之後會寫一篇關於自訂:Vue Directive
  */
  directives: {
    focus (el, { value }, { context }) {
      if (value) {
        context.$nextTick(() => {
          el.focus();
        })
      }
    }
  },
  methods: {
    ...mapActions([
        'toggleTodo',
        'deleteTodo',
        'updateTodo'
    ]),
    showEditMode () {
      this.updateMode = true;
    },
    actionEdit (e) {
      const userChange = e.target.value.trim();
      console.log('userChange', this.item.key, userChange);
      this.updateTodo({
        key: this.item.key,
        change: userChange
      });
      this.updateMode = false;
    },
    cancelEdit (e) {
      e.target.value = this.item.title;
      this.updateMode = false;
    }
  }
}
</script>

<style>
  .edit-input{
    line-height: 1em;
  }
</style>

updateMode 修改模式

  1. 預設將原本的 value bind 到 input 裡面
  2. 按下 enter 的時候觸發修改
  3. 游標離開與按下 esc 的時候取消修改
  4. v-focus 為切換之後 focus input 功能

actionEdit

  • action 只能帶一個 value 因此包裝成 object
    • key: 搜尋 todo
    • update: 使用者更新後的 todo content

cancelEdit

使用者點到了其他地方或者按下 ESC 想取消修改,因此必須把 input 的 value 修改回去,不然下次打開會是上次未修改成功的值。


action.js

export const updateTodo = ({ commit }, obj) => {
  console.log('updateTodo', obj);
  commit(types.UPDATE_TODO, obj);
}

mutations.js

// 更新
[types.UPDATE_TODO] (state, obj) {
  for(var i in state.todos){
    var item = state.todos[i];
    if ( item.key === obj.key){
      console.log('UPDATE_TODO:', item.content, ' to →', obj.change);
	  // 找到 todo 更新 content
      state.todos[ i ].content = obj.change;
      break;
    }
  }
},

Todo list 簡單範例就到這邊。


github 完整範例:

實作小範例入門 Vue & Vuex 2.0 - github 完整範例

使用 git checkout 切換每天範例。


上一篇
vue & vuex 14 - 經典 todo list - II (改變狀態與 delete)
下一篇
vue & vuex 16 - 自訂按鈕 custom checkbox ($emit, $on)
系列文
實作小範例入門 Vue & Vuex 2.030

2 則留言

0
LeoYeh
iT邦新手 5 級 ‧ 2017-05-04 16:20:27

感謝大大 精彩教學
以下有兩個錯誤
1.metutions.js -> 應為 mutations.js
2.mutations.js 中的 接收物件屬性 應為 obj.change 不是 obj.update
:)

Jacky iT邦新手 5 級 ‧ 2017-05-04 16:40:42 檢舉

感謝 LeoYeh 大大精闢的指正,太感謝了!!

立即修正文章內容。

0
garrodran99
iT邦新手 5 級 ‧ 2017-10-24 22:42:28

Esc取消時
e.target.value = this.item.title;

應該是 e.target.value = this.item.content;

是content 不是 title 吧

我要留言

立即登入留言