第18回 Vue.js入門 Vue Routerで サンプルプログラムを作成

Photo by fran innocenti on Unsplash プログラミング

こんにちは、コンスキです。

今回は、ここまでで知ったVue Routerの機能を使って、サンプルプログラムを作ります。

サンプルプログラムの完成形

これから作るサンプルプログラムが完成すると次のようになります。

プログラムの内容としては、やることリストを使うためにログインを必要とするようなプログラムです。

ログインせずにやることリストを使おうとすると、ログインを求められます。

ログインに必要なユーザーIDは「konsuki」、パスワードは「vue.js」です。

ログインが完了すると、やることリストが表示され、使えるようになります。

サンプルプログラムの内容

サンプルプログラムのコードは次のようになります。今回は、コードが非常に長いため、htmlファイルとJavaScriptファイルとcssファイルに分けています。

htmlファイル

<!DOCTYPE html>
<html lang="ja"
<head>
  <meta charset="UTF-8">
  <title>サンプルプログラム</title>
  <link rel="stylesheet" href="CSSファイルのパス">
  <script src="https://unpkg.com/vue"></script>
  <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> 
</head>
<body>
  <div id="app">
    <nav v-cloak>
      <router-link to="/top">トップページ</router-link>
      <router-link to="/todolist">やることリスト</router-link>
      <router-link to="/login" v-show="!Auth.loggedIn()">ログイン</router-link>
      <router-link to="/logout" v-show="Auth.loggedIn()">ログアウト</router-link>
    </nav>
    <router-view></router-view>
  </div>
  
  
  <!-- ToDoリストページのテンプレート -->
  <script type="x-template" id="todo-list">
    <div>
      <label v-for="label in options">
        <input type="radio" v-model="current" v-bind:value="label.value">{{ label.label }}
      </label>
      <p>{{ computedTodos.length }} 件を表示中</p>
      <table>
        <thead>
          <tr>
            <th class="id">ID</th>
            <th class="comment">コメント</th>
            <th class="state">状態</th>
            <th class="button">-</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="item in computedTodos" v-bind:key="item.id">
            <th>{{ item.id }}</th>
            <td>{{ item.comment }}</td>
            <td class="state">
              <button v-on:click="doChangeState(item)">{{ labels[item.state] }}</button>
            </td>
            <td class="button">
              <button v-on:click.ctrl="doRemove(item)">削除</button>
            </td>
          </tr>
        </tbody>
      </table>
      <p>※削除ボタンはコントロールキーを押しながらクリックして下さい</p>

      <h2>新しい作業の追加</h2>
      <form class="add-form" v-on:submit.prevent="doAdd">
        コメント<input type="text" ref="comment">
        <button type="submit">追加</button>
      </form>
    </div>
  </script>  
  
  <!-- ログインページのテンプレート -->
  <script type="x-template" id="login">
    <div>
      <h1>ログインページ</h1>
      <p v-if="$route.query.redirect">
        ログインを行なって下さい。
      </p>
      <form @submit.prevent="login">
        <label><input v-model="userId" placeholder="ユーザーID"></label>
        <label><input v-model="pass" placeholder="password" type="password"></label>
        <br>
        <button type="submit">ログイン</button>
        <p v-if="error" class="error">ログインに失敗しました</p>
      </form>
    </div>
  </script>
  <script src="JavaScriptファイルのパス"></script>
</body>
</html>

JavaScriptファイル

// 擬似的なログイン機能のためのオブジェクト
var Auth = {
  login: function (userId, pass, cb) {

    setTimeout(function () {
      if (userId === "konsuki" && pass === "vue.js") {
        // ログイン後、tokenはローカルストレージに保存されます
        localStorage.token = Math.random().toString(36).substring(7)
        if (cb) { cb(true) }
      } else {
        if (cb) { cb(false) }
      }
    }, 0)
  },

  logout: function () {
    delete localStorage.token
  },

  loggedIn: function () {
    // tokenがローカルストレージにあったときのみ、ログイン状態と判断します
    return !!localStorage.token
  }
}

// ログインページのコンポーネント
var Login = {
  template: "#login",
  data: function () {
    return {
      userId: "",
      pass: "",
      error: false
    }
  },
  methods: {
    login: function () {
      Auth.login(this.userId, this.pass, (function (loggedIn) {
        if (!loggedIn) {
          this.error = true
        } else {

          this.$router.replace(this.$route.query.redirect || "/")
        }
      }).bind(this))
    }
  }
}

// https://jp.vuejs.org/v2/examples/todomvc.html
var STORAGE_KEY = "todos-vuejs-demo"
var todoStorage = {
  fetch: function() {
    var todos = JSON.parse(
      localStorage.getItem(STORAGE_KEY) || "[]"
    )
    todos.forEach(function(todo, index) {
      todo.id = index
    })
    todoStorage.uid = todos.length
    return todos
  },
  save: function(todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}

// やることリストページのコンポーネント
var TodoList = {
  template: "#todo-list",
  data: function () {
    return {
      loading: false,
      users: function () {
        return []
      },
      error: null,
      todos: [],
      options: [
        { value: -1, label: "すべて" },
        { value: 0, label: "作業中" },
        { value: 1, label: "完了" }
      ],
      current: -1  
    }
  },
  computed: {
    computedTodos: function () {
      return this.todos.filter(function (el) {
        return this.current < 0 ? true : this.current === el.state
      }, this)
    },
    labels: function () {
      return this.options.reduce(function (a, b) {
        return Object.assign(a, { [b.value]: b.label })
      }, {})
    }
  },
  methods: {
    doAdd: function (event, value) {
      var comment = this.$refs.comment
      if (!comment.value.length) {
        return
      }
      this.todos.push({
        id: todoStorage.uid++,
        comment: comment.value,
        state: 0
      })
      comment.value = ""
    },
    doChangeState: function (item) {
      item.state = item.state ? 0 : 1
    },
    doRemove: function (item) {
      var index = this.todos.indexOf(item)
      this.todos.splice(index, 1)
    }
  },
  watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },

  //やることリストページのコンポーネントのインスタンスが作られるとき、やることリストの作業項目がローカルストレージから取り出されます
  created: function () {
    this.todos = todoStorage.fetch()
  }
}


var router = new VueRouter({
  routes: [
    {
      path: "/top",
      component: {
        template: `
          <div>
            <h1>トップページ</h1>
            <p>ログインを行なってから、やることリストをご利用下さい。</p>
          </div>
        `
      }
    },
    {
      path: "/todolist",
      component: TodoList,
      beforeEnter: function (to, from, next) {
        // ログインしていない状態でこのページに遷移した場合、ログインページにリダイレクトされます
        if (!Auth.loggedIn()) {
          next({
            path: "/login",
            query: { redirect: to.fullPath }
          })
        } else {
          next()
        }
      }
    },
    {
      path: "/login",
      component: Login
    },
    {
      path: "/logout",
      beforeEnter: function (to, from, next) {
        Auth.logout()
        next("/top")
      }
    },
    {
      // 未設定のルートのURL(path)へルーティングがあった場合は、トップページへリダイレクトします
      path: "*",
      redirect: "/top"
    }
  ]
})



var app = new Vue({
  el: "#app",
  data: {
    Auth: Auth
  },
  router: router
})

cssファイル

[v-cloak] {
  display: none
}

やることリストページのコンポーネントを作成するときに、こちらのページの作り方を参考にさせていただきました。

v-cloakディレクティブ

今回のサンプルプログラムのHTMLでは、初めの方のnav要素にv-cloakという見たことがないディレクティブが使われています。

v-cloakディレクティブは、Vue.jsのディレクティブとして単体で取り上げて説明しませんでした。

便利でアプリケーションの質を上げる事のできるディレクティブですが、アプリケーションの作成に必須ではなさそうだったためです。

そのため、ここで少しv-cloakディレクティブの説明をします。

Vue.jsを使ったWebページはページ読み込まれたあとに少しだけ、Vueインスタンスが生成される前のただの要素として、コンポーネントや要素が表示されることがあります。

例えば、次の様に「{{}}(マスタッシュタグ)」を使った部分が表示されてしまうことがあります。


一瞬とはいえ、どうせだったら表示されない方がいいですよね。

そんなときに、Vueインタンス用意されるまでは、ブラウザに表示させないようにしてくれるディレクティブがv-cloakディレクティブです。

使い方

まず、Vueインスタンスがコンパイルされて用意されるまで表示したくない要素に、v-cloakディレクティブを付けます。

<要素名 v-cloak>コンパイルされるまで表示したくないもの</要素名>

その後、cssに次のようなスタイルを追加します。

[v-cloak] {
  display: none;
}

これだけで、ページを読み込んだ後にVueインスタンスがコンパイルされる前の要素が一瞬表示されてしまうことを防ぐことができます。

スタイルを追加する際は、cssファイルの中やHTML中の<style>要素の中に上のスタイルを追加して下さい。

次のようにインラインにスタイルを設定しても、v-cloakディレクティブは有効にならないため注意して下さい。

<要素名 v-cloak style="display: none;">

コメント

タイトルとURLをコピーしました