vue.jsを使ってシングルアプリケーションを作るためには、HTMLのtable要素に表示されたセルの更新を別ページに移動することなく更新したいものです。

入門者を対象に今回はシンプルな方法でvue.jsを利用して更新可能なセルを作成します。

バックエンドについて

バックエンドではLaravelを用いていますが、vue.jsからのデータ取得/更新/削除処理を受けつけることができればLaravelである必要はないためサーバ側の処理については一切明記していません。

テーブルと初期設定について

htmlのtableの作成は、usersテーブルを使って行います。usersテーブルは、ユーザ名とEmailで構成されています。

テーブルのデータはaxiosを通してVueインタンスのmountedフックで取得します。UserTableという名前のコンポーネントを利用して、v-forでtr要素を作成します。


var app = new Vue({
  el: '#app',
  data: {
    users: []
  },
  components: {
  	UserTable
  },
  mounted: function(){
    axios.get('/api/users').then(response => this.users = response.data);
  }
})

HTMLは下記のように記述します。


<div id="app">
	<table class="table table-bordered">
		<thead  class="thead-dark">
			<tr>
				<th>ID</th>
				<th>ユーザ名</th>
				<th>E-mail</th>
				<th>削除</th>
			</tr>
		</thead>
		<tbody>
			<tr is="user-table" v-for="(user,index) in users" v-bind:user="user" v-bind:key="user.id" v-bind:index="index"></tr>
		</tbody>
	</table>
</div>

UserTable.vueの中身は下記の通りです。


<template>
<tr>
    <td>{{ user.id }}</td>
    <td>{{ user.name }}</td>
    <td>{{ user.email }}</td>
    <td><button class="btn btn-danger">削除</button></td>
</tr>
</template>

<script>
    export default {
        props : ['user','index']
    }
</script>

ここまでの記述では特別な処理は行わずv-forを使ってaxiosから取得したusersデータを展開して、テーブルとして表示しているだけです。

ブラウザで確認すると下記のように表示されます。削除ボタンが付いていますが、何も設定されていないのでクリックしても変化はありません。

ユーザテーブルを表示
ユーザテーブルを表示

テーブルの列の削除

最初に削除の機能を追加します。

UserTable.vueのbutton要素にv-on:clickイベントと追加し、クリックを行うと行の番号であるindexとuserのidをdeleteRowメソッドに渡します。


<button class="btn btn-danger" v-on:click="deleteRow(user.id,index)">削除</button>

deleteRowメソッド内ではaxiosによりその行のdelete処理を行い、$emitメソッドを使って親要素に行が削除されたことを伝えます。


methods: {
    deleteRow : function(id,index){
        axios.delete('api/users/' + id)
        .then((response) => {
            this.$emit('from-child',index)
        }).catch((error) =>{
            console.log(error)
        })
    },
},

親側ではUserTableコンポーネントから送られてくるイベントfrom-childを受け取ってdeleteRowを実行します。

UserTableコンポーネントと親側のdeleteRowは名前は一緒ですが別の物です。

<tr is="user-table" v-for="(user,index) in users" v-bind:user="user" v-bind:key="user.id" v-bind:index="index" v-on:from-child="deleteRow"></tr>

親側のdeleteRowで行の削除を行います。削除機能の作業は完了です。


methods: {
	deleteRow : function(index){
    this.$delete(this.users,index)
	}
},

テーブルのtd要素の更新

tdをクリックするとinput要素に変える

td要素をダブルクリックするとinput要素に変更する機能を追加します。

input要素の表示・非表示はv-ifディレクティブを使用します。v-ifディレクティブを切り替えるための値として、ユーザ名用にisEditName、email用にisEditEmailというdataを追加し、初期値はfalseとします。


data: function(){
    return {
      isEditName : false,
      isEditEmail : false
    }
}

v-ifとisEditNameを組み合わせるとisEditNameがfalseの場合は通常の表示、isEditNameがtrueの場合は、input要素が表示されるようにします。


<td v-if="!isEditName">{{ user.name }}</td>
<td v-else><input type="text" class="form-control" v-model="user.name"></td>

v-ifによってinput要素の非表示・表示を行うためには、isEditNameをfalseからtrueに切り替える仕組みを入れる必要があります。そのために利用するのがdoubleclickイベントです。文字列が入ったtd要素をダブルクリックするとisEditNameがfalseからtrueになりinput要素が表示されます。


<td v-if="!isEditName" v-on:dblclick="isEditName = true">{{ user.name }}</td>
<td v-else><input type="text" class="form-control" v-model="user.name"></td>

先頭のユーザ名をダブルクリックするとinput要素に変わります。

ダブルクリックでinput要素表示
ダブルクリックでinput要素表示

input要素を元の状態に戻す

td要素をダブルクリックをするとinput要素に変わりました。input要素の中身を更新した後にはinput要素から元の状態に戻す必要があります。元の状態に戻すためblurイベントを利用します。input要素にblurイベントを入れることでダブルクリックでinput要素にしたあと、一度input要素にカーソルを合わせて外すとisEditNameがfalseになります。isEditNameがfalseになるのでv-ifディレクティブによりinput要素から元の状態に戻ります。


<td v-if="!isEditName" v-on:dblclick="isEditName = true">{{ user.name }}</td>
<td v-else><input type="text" class="form-control" v-model="user.name" v-on:blur="isEditName=false" ></td>

input要素の更新を反映させる

input要素の切り替えができるようになったので、最後は更新した内容をデータベースに反映させるための処理が必要になります。先程設定したblurイベントを利用しますが、今回は新規のメソッドを追加します。

メソッドの名前はupdateNameとして、変更を行ったuser.idとuser.nameを渡します。


<td v-if="!isEditName" v-on:dblclick="isEditName = true">{{ user.name }}</td>
<td v-else><input type="text" class="form-control" v-model="user.name" v-on:blur="updateName(user.id, user.name)" ></td>

axiosでサーバに対して変更のpatchリクエストを送ります。リクエストが成功した時のみisEditNameの値をfalseにします。

サーバ側の処理はここでは説明しませんが、それぞれの環境に合わせた処理を追加してください。

updateName : function(id,name){
    axios.patch('api/users/' + id, {id : id, name: name})
    .then((response) => {
        this.isEditName = false
     }).catch((error) =>{
        console.log(error)
      })
}

通常ではサーバ側との通信の障害やバグまたValidationによってサーバ側での更新が行われない場合も考えられます。成功した場合には成功メッセージ、失敗した場合にはエラーメッセージを表示する機能をつける必要があります。

実際に更新を行って反映されるか確認しましょう。ユーザIDの1のユーザの名前を山岸から山本に変更します。変更後input要素からカーソルを外すとサーバへの更新が行われます。

セルの更新を行う
セルの更新を行う

axiosを使って戻される値で更新されたかどうか判断する必要がありますが、ブラウザを再読み込みして更新データが表示されれば問題なくデータ更新は反映されています。

emailにも同じ設定

E-mail列の情報も書き換えができるように下記のように設定を行います。


<td v-if="!isEditEmail" v-on:dblclick="isEditEmail = true">{{ user.email }}</td><td v-else><input type="text" class="form-control" v-model="user.email" v-on:blur="updateEmail(user.id, user.email)" ></td>

email用のupdateEmailも追加します。


updateEmail : function(id,email){
    axios.patch('api/users/' + id, {id : id, email: email})
    .then((response) => {
        this.isEditEmail = false
        console.log(response)
    }).catch((error) =>{
        console.log(error)
    })
},

これでnameとemailのセルをダブルクリックすると変更ができるようになりました。