mmyoji's diary

プログラミングとか日々のどうでもいいこととか

今更Vue.js始めた(Todoアプリちょっとだけ) - その2

前回 の続きです。

  • 各todoの操作
    • 削除
    • 編集

をできてなかったので実装していきます。

前回までのコード

index.html(bodyタグの内側)

    <section class="todoapp" id="todoapp">
      <header class="header">
        <h1>Todos</h1>
        <input class="new-todo" type="text" v-model="newTodo"
               v-on="keyup: addTodo | key 'enter'"
               placeholder="What needs to be done?" />
      </header>

      <section class="main">
        <input class="toggle-all" type="checkbox" />
        <label for="toggle-all">Mark all as completed</label>
        <ul class="todo-list">
          <li v-repeat="todo: todos"
              v-class="completed: todo.completed">
            <div class="view">
              <input class="toggle" type="checkbox" v-model="todo.completed" />
              <label v-text="todo.title"></label>
              <button class="destroy"></button>
            </div>
            <input class="edit" type="text" />
          </li>
        </ul>
      </section>
    </section>
    <script src="./js/build/bundle.js"></script>

js/src/main.js

import Vue from "vue"

let Main = new Vue({
  el: "#todoapp",

  data: {
    todos: [],
    newTodo: ""
  },

  methods: {
    addTodo(event) {
      event.preventDefault()
      let newMsg = this.newTodo.trim()
      if ( !newMsg ) return

      this.todos.push({ title: newMsg, completed: false })
      this.newTodo = ""
    }
  }
});

Todoの削除

xボタンがクリックされたら要素を消す、って処理になります

<button class="destroy" v-on="click: removeTodo(todo)"></button>

でjsはさらっと

// methods: の中にメソッドを書いていくんでしたね
  methods: {
    // addTodo(event) {


    removeTodo(todo) {
      // $removeというメソッドが用意されています。便利。
      this.todos.$remove(todo)
    }

以上です!簡単ですね!

編集

これが自分で書くときさらっと書けなさそうな感じでした...

手順を以下に書いておきます

  1. .edit 要素に todo.title をbinding ( v-model="todo.title" )
  2. todoリストの文字部分( label )がダブルクリック( v-on="dblclick: " )されたら編集中todo( editedTodo )にそれ自身を入れる
  3. リストがedidt用viewに入れ替わり, edit用のインプットにフォーカスが当たる
    • リストのクラスを .editing にする
    • カスタムディレクティブ v-todo-focus を定義
  4. Enter keyで編集終了、esc keyで編集キャンセル

「?」って感じのとこが何箇所かあったかもしれませんが一つずつやっていきましょう!

1. edit要素にtodo.titleをbinding

<input class="edit" type="text" v-model="todo.title" />

一瞬ですね。

2. todoリストの文字部分がダブルクリックされたら編集中todoにそれ自身を入れる

<label v-text="todo.title" v-on="dblclick: editTodo(todo)"></label>

で、この editTodo を定義します

  data: {
    todos: [],
    newTodo: "",
    editedTodo: null // 編集中のTodo
  },

dataに editedTodo という、編集中のtodoを表すpropertyを追加します。初期値はnullです。

    // addTodo()

    editTodo(todo) {
      this.beforeEditCache = todo.title
      this.editedTodo = todo
    },

    // removeTodo()

そして methodseditTodo を定義します。

引数に todo オブジェクトを取り、文字通り beforeEditCache (編集前のtodoのtitle) を入れておき、先の data 内に定義した editedTodo に引数として渡されたtodoを格納します。

で実はhtml側にちょっと細工をしないと思い通りには動いてくれません

<li v-repeat="todo: todos"
    v-class="completed: todo.done, editing: todo == editedTodo">

todo == editedTodo だった時は editing というclassが適用されるようになります。

これで編集したいtodoをダブルクリックするとviewが切り替わるようになると思います。

3. リストがedidt用viewに入れ替わり, edit用のインプットにフォーカスが当たる

何を言ってるんだ、という感じですがここで v-textv-on などのディレクティブを自作してみます。

  // data: {
  // },

  directives: {
    "todo-focus": function(value) {
      if (!value) return

      setTimeout(() => { this.el.focus() }, 0)
    }
  },

v-todo-focus という名前のdirectiveを作成しました。valueが評価された場合、その要素にfocusを当てるというdirectiveです。

<input class="edit" type="text" v-model="todo.title"
       v-todo-focus="todo == editedTodo" />

このようにtodoとeditedTodoが一致した場合に value がtrueとなるわけですね。実際にまたreloadしてさっきと比べてみてください。クリックした後にそのまますぐに入力を開始できるようfocusが当たるはずです。

4. Enter keyで編集終了、esc keyで編集キャンセル

これで最後になります。

仕様として、

  • 編集しているinputからfocusが外れたら編集終了
  • もしくは編集中にEnterが押されたら編集終了
  • もしくはEsc keyが押されたら編集キャンセル(編集前の状態に戻す)

があるとします。

編集終了 (doneEdit) と 編集キャンセル (cancelEdit) メソッドがあれば実現できそうです

html側から書いていきましょう

            <input class="edit" type="text" v-model="todo.title"
                   v-todo-focus="todo == editedTodo"
                   v-on="
                         blur: doneEdit(todo),
                         keyup: doneEdit(todo) | key 'enter',
                         keyup: cancelEdit(todo) | key 'esc'
                   " />

もうほとんど説明はいらないと思います。仕様通りです。

次にメソッドを作りましょう。

     doneEdit(todo) {
      if (!this.editedTodo) return

      this.editedTodo = null
      todo.title = todo.title.trim()
      if (!todo.title) {
        this.removeTodo(todo)
      }
    },

    cancelEdit(todo) {
      this.editedTodo = null
      todo.title = this.beforeEditCache
    },

結構シンプルで読みやすいかと思います。 doneEdit ではtitleがなければその要素をそのまま消しちゃうって仕様になってます。

おわり

以上になります。他にもfilterを使ったり、computed propertyを使ったりとしているので最初に貼っておいたリンクを辿って一通り全部触っておくと大抵のjsは書けるんじゃないかなーと勝手に思ってます。

次は最近僕がやったことですが、jQueryからの置き換え例とかを書ければなぁと思っています。そんな大した例はないのですが...