Vue.js TODO管理アプリを作成する(その2:いったんの完成)

前回、TODO管理アプリはTODO一覧を表示するところまで出来ていた。それを改造して、新規作成と編集ができるようにする。

新規作成画面

新規作成画面と確認画面を用意する。新規作成画面はフォームを2つ用意して入力させる。OKボタンを押すと、確認画面へ遷移する。

ここでは、新規作成画面で入力した値を確認画面に渡すところで苦戦した。以下の3つの方法を思いついた。

  • コンポーネント間通信でデータを受け渡し
  • 親コンポーネントに新規TODOのデータを持たせる
  • storeオブジェクトに保存

コンポーネント間通信は、画面遷移すると前の画面のコンポーネントが消えるようで、うまく動かなかった。2つのコンポーネントが同じ画面に表示されている状態ならば、うまく通信ができる。次の親コンポーネントにデータを持たせる方法は、親コンポーネントが肥大化してしまう。ということで、最後のstoreオブジェクトを使う方法に落ち着いた。

var store = {
  todo: {
    title: '',
    detail: ''
  },
  setTodo (title, detail) {
    this.todo.title = title;
    this.todo.detail = detail;
  },
  clearTodo () {
    this.todo.title = ''
    this.todo.detail = ''
  }
}

このstoreオブジェクトはデータの入れ物として使うだけでなく、状態管理のためにも使われる。将来的にはVuexなども取り入れたい。

参考:
コンポーネント 親子間以外の通信 - Vue.js
状態管理 - Vue.js
Vuex Introduction · GitBook

編集ボタン

編集ボタンのリンクは、パラメータをつけられるようにした。"/edit/1"といったURLとなる。routerに設定し、パラメータを渡せるリンクを用意する。

const routes = [
  { path: '/', component: Todos },
  { path: '/new', component: New },
  { path: '/newConfirm', component: Confirm },
  { path: '/edit/:item', name:'edit', component: Edit }
]
<router-link :to="{ name:'edit', params:{item:index} }">edit</router-link>

このパラメータは、$routerから取り出すことができる。

var index = this.$router.currentRoute.params.item;

いったんの完成

SPAで動くTODO管理アプリが完成した。TODOの新規追加と編集ができる。下に、現時点のソースコードを載せている。

完成したと言っても、まだまだ改善の余地がある。次回は、単一ファイルコンポーネントに挑戦しようと思う。

参考:
単一ファイルコンポーネント - Vue.js

ここまでのソースコード

<!DOCTYPE html>
<html>
<head>
  <title>TODO Sample</title>
  <meta charset="utf-8" />
  <script src="https://unpkg.com/vue"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
  <!-- HTMLを書く -->
  <div id="app">
    <h1>初めてのTODOアプリ</h1>
    <p>
      <router-link to="/">Go to Top</router-link>
    </p>

    <router-view 
      v-bind:todos="todos"
      v-on:add-new-todo='addNewTodo'
      v-on:edit-new-todo='editNewTodo'
       ></router-view>
  </div>

  <!-- トップページのテンプレート -->
  <script type="text/x-template" id="todo-list-template">
    <div>
      <p>TODO一覧</p>
      <template v-for="(item, index) in todos" >
        <span>
          {{ item }}  
          <router-link :to="{ name:'edit', params:{item:index} }">edit</router-link><br/>
        </span>
      </template>
      <input type="button" value="新規作成" v-on:click="goNewTask()">
     </div>
  </script>

  <!-- 新規作成画面のテンプレート -->
  <script type="text/x-template" id="todo-new-template">
    <div>
      <p>TODO新規作成</p>
      タスク名<br>
      <input type="text" name="title" size="30" maxlength="20" v-model="title"><br><br>
      詳細<br>
      <input type="text" name="detail" size="45" maxlength="40" v-model="detail"><br><br>
      <input type="button" value="作成する" v-on:click="buttonClick()">
     </div>
  </script>

  <!-- 新規作成確認画面のテンプレート -->
  <script type="text/x-template" id="todo-new-confirm-template">
    <div>
      <p>
        タスク名<span>{{ this.title }}</span><br>
        詳細<span>{{ this.detail }}</span><br>
      <p>
      <p>本当にこのタスクを作成しますか<br>
        <input type="button" value="OK" v-on:click="okButtonClick()">
        <input type="button" value="戻る" v-on:click="cancelButtonClick()">
      </p>
     </div>
  </script>

  <!-- 編集画面のテンプレート -->
  <script type="text/x-template" id="todo-edit-template">
    <div>
      <p>編集画面</p>
      タスク名<br>
      <input type="text" name="title" size="30" maxlength="20" v-model="title"><br><br>
      <input type="button" value="編集完了する" v-on:click="okButtonClick()">
    </div>
  </script>

  <script type="text/javascript">
    var store = {
      todo: {
        title: '',
        detail: ''
      },
      setTodo (title, detail) {
        this.todo.title = title;
        this.todo.detail = detail;
      },
      clearTodo () {
        this.todo.title = ''
        this.todo.detail = ''
      }
    }

    // トップページのコンポーネント
    const Todos = {
      template: '#todo-list-template',
      props: ['todos'],
      data: function (){
        return { }
      },
      methods: {
        goNewTask: function() {
          store.clearTodo();
          router.push('/new');
        }
      },
      created: function () {
      }
    }

    // 新規作成画面のコンポーネント
    const New = { 
      template: '#todo-new-template',
      data: function (){
        return {
          title: "", detail: ""
         }
      },
      methods: {
        buttonClick: function() {
          console.log("new button click");
          console.log("new todo is " + this.title + "," + this.detail);
          store.setTodo(this.title, this.detail);
          router.push('/newConfirm');
        }
      },
      created: function () {
        this.title = store.todo.title;
        this.detail = store.todo.detail;
      }
    }

    // 確認画面のコンポーネント
    const Confirm = { 
      template: '#todo-new-confirm-template',
      data: function (){
        return {
          title: "", detail: ""
         }
      },
      methods: {
        okButtonClick: function() {
          console.log("ok button click");
          this.$emit('add-new-todo', this.title, this.detail);
          router.push('/');
        },
        cancelButtonClick: function() {
          console.log("cancel button click");
          router.go(-1); // 1つ戻る
        }
      },
      created: function () {
        this.title = store.todo.title;
        this.detail = store.todo.detail;
      }
    }

    // 編集画面のコンポーネント
    const Edit = { 
      template: '#todo-edit-template',
      props: ['todos'],
      data: function (){
        var index = this.$router.currentRoute.params.item;
        return {
          title: this.todos[index]
         }
      },
      methods: {
        okButtonClick: function() {
          console.log("ok button click");
          var index = this.$router.currentRoute.params.item;
          this.$emit('edit-new-todo', index, this.title);
          router.push('/');
        }
      },
      created: function() {
      }
    }
    
    const routes = [
      { path: '/', component: Todos },
      { path: '/new', component: New },
      { path: '/newConfirm', component: Confirm },
      { path: '/edit/:item', name:'edit', component: Edit }
    ]
    const router = new VueRouter({
      routes // routes: routes の短縮表記
    })
    const app = new Vue({
      router,
      message: "message",
      data: {
        todos: [
          "todo1",
          "todo2",
          "todo3"
        ]
      },
      methods:{
        addNewTodo: function(t, d) {
          console.log("addNewTodo "+t+":"+d);
          this.todos.push(t);
        },
        editNewTodo: function(index, title) {
          console.log("editNewTodo "+index+":"+title);
          this.todos[index] = title;
        }
      }
    }).$mount('#app')
  </script>
</body>
投稿日 2017年05月28日