
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、気がつけば今年も上半期が終了し、さらに令和の時代になってから2ヵ月が過ぎてしまい時間のスピードに驚く今日この頃です。
そして、さらに時間の流れを感じるのが「Vueの次期バージョン3.0」のニュースをちらほらと見るようになってきたことです。(どうやら次期バージョンの仕様で不満が出たりもしているようですが…)
私が3.0のニュースははじめに見たのが2018年の年末でしたので、やはりすでに半年がビューーン!と過ぎてしまったことになります。
正直なことを言うとこのブログでもVueの記事をたくさん公開してきましたが、最近サボってますね(すみません。Laravel
or その他が面白くてちょっとした浮気・・・)
でもたまにはVue
も使っていないと、スキルが低下していくことは間違いないので、ここいらでその昔きちんと実行可能かどうかをチェックしておきたかった「ページ離脱時の挙動」を紹介したいと思います。
ぜひ皆さんのお役に立てると嬉しいです!
(最後でソースコードをダウンロードすることができます)
実行環境: Google Chrome 75、Firefox 68、IE 11
やりたいこと
わかりやすい例で言うと、wordpress
で記事を書いていて保存していないのに閉じようとすると「まだ記事が保存されてないけどホントに大丈夫!?」と警告を出してくれますが、この機能をVueを使って実装してみたいと思います。(つまり、何も入力がなければ警告は出さないようにします)
なお、今回は以下の2バージョンを作ってみることにしました。
- シンプルバージョン
- 厳密バージョン
また、「ページ離脱の警告」が実行されるのは以下の3パターンです。
- リンクから他のページへ移動しようとした
- ページが再読み込みされた
- タブを閉じようとした
なお、擬似的に「保存する」ボタンをクリックしたら警告は出さないようにしますが、さらに入力データされてページ離脱しようとすると警告が出るようにします。
では、ひとつずつ見ていきましょう。
ページ離脱をキャッチして警告を表示するコードをつくる
シンプルバージョン
シンプルバージョンは、入力データに変更があった時点で「ページ離脱の警告」を有効にする、シンプルな内容になっています。
<!DOCTYPE html>
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="container">
<span class="badge badge-primary">サンプル - 1</span>
<h3>入力内容を変更するとページ移動する前にアラートが出ます。</h3>
<div class="row">
<div class="col-md-6 form-group">
<label>名前</label>
<input class="form-control" type="text" v-model="params.name">
</div>
<div class="col-md-6 form-group">
<label>かな</label>
<input class="form-control" type="text" v-model="params.name_kana">
</div>
</div>
<div v-if="dirty">⚠ 保存されていないデータがあります</div>
<hr>
<ul>
<li>
パターン1: <a href="https://yahoo.co.jp">他のページへ移動します</a>
</li>
<li>
パターン2: <a href="#" @click.prevent="location.reload()">リロードします</a>
</li>
<li>
パターン3: タブを閉じる(閉じるボタンをクリックしてください)
</li>
</ul>
<br>
<button type="button" class="btn btn-warning" @click="save">保存する</button>(実際には保存はしません)
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
params: {
name: '',
name_kana: ''
},
dirty: false
},
methods: {
save: function() {
// ここで保存するコード
this.dirty = false;
}
},
mounted: function() {
var self = this;
window.onbeforeunload = function() {
if(self.dirty) {
return '未保存のデータがあります。処理を実行しますか?';
}
};
},
watch: {
params: {
handler: function() {
this.dirty = true;
},
deep: true
}
}
});
</script>
</body>
</html>
実際に表示はこちら↓↓↓
watchについて
この中で重要なのが、watch
の部分です。
このメソッドはVue
の変数params
に変更があったときに実行されるようになっていますが、おそらく通常使っているwatch
メソッドとは違っていると思います。
なぜなら、params
は単体のデータを持つ通常の変数ではなく、オブジェクト(今回ではname
, namae_kana
の2つデータを持っています)だからです。
つまり、watch
にオブジェクトのデータを設定するには、deep
オプションをtrue
にし、さらにメソッドはhandler()
を指定する必要があるのです。
そして、この中では変数dirty
をtrue
にして、データ変更があったかどうかをチェックするようにしています。
onbeforeunloadについて
次にonbeforeunload
ですが、以下ように値をreturn
で返していますが実際にはGoogle Chrome
、Firefox
ではこの文字列が影響されることはなく、IEのみが有効になっています。
if(self.dirty) {
return '未保存のデータがあります。処理を実行しますか?';
}
※ ただし、必要ないからといって何も返さないようにするとページ離脱の警告が実行されなくなってしまうので気をつけてください。
save()について
save()
内ではAjax
通信などでデータ送信することを想定しています。そのため、もしaxios
を使った場合は以下のようになります。
var self = this;
axios.post('/url')
.then(function(response){
self.dirty = false;
});
※ IEではPromise
に対応していないため、axios
がそのままでは動きません。その場合はes6-promiseを使ってください。
厳密バージョン
続いて厳密バージョンです。
先ほどのシンプルバージョンでは、入力データに変更があった場合に必ず警告を有効にしますが、この場合、もし一旦変更して元に戻した場合でも警告が表示されてしまう可能性がでてきます。
そのため、厳密バージョンでは「当初のデータと今のデータが同じかどうか」をチェックするようにしています。
<!DOCTYPE html>
<html>
<head>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="container">
<span class="badge badge-primary">サンプル - 2</span>
<h3>入力内容を変更するとページ移動する前にアラートが出ます。</h3>
<div class="row">
<div class="col-md-6 form-group">
<label>名前</label>
<input class="form-control" type="text" v-model="params.name">
</div>
<div class="col-md-6 form-group">
<label>かな</label>
<input class="form-control" type="text" v-model="params.name_kana">
</div>
</div>
<div v-if="dirty">⚠ 保存されていないデータがあります</div>
<hr>
<ul>
<li>
パターン1: <a href="https://yahoo.co.jp">他のページへ移動します</a>
</li>
<li>
パターン2: <a href="#" @click.prevent="location.reload()">リロードします</a>
</li>
<li>
パターン3: タブを閉じる(閉じるボタンをクリックしてください)
</li>
</ul>
<br>
<button type="button" class="btn btn-warning" @click="save">保存する</button>(実際には保存はしません)
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
params: {
name: '',
name_kana: ''
},
originalParams: {}
},
methods: {
copyParams: function() {
this.originalParams = JSON.parse(JSON.stringify(this.params));
},
save: function() {
// ここで保存するコード
this.copyParams();
}
},
mounted: function() {
var self = this;
window.onbeforeunload = function() {
if(self.dirty) {
return '未保存のデータがあります。処理を実行しますか?';
}
};
this.originalParams = JSON.parse(JSON.stringify(this.params));
},
computed: {
dirty: function() {
return (JSON.stringify(this.params) !== JSON.stringify(this.originalParams));
}
}
});
</script>
</body>
</html>
先ほどのシンプルバージョンとの大きな違いは、watch
がなくなり代わりにcomputed
が登場しているところです。
つまり、dirty
は「当初のデータと今のデータが同じかどうか」をチェックしてtrue
/false
を返すのですが、この比較をJSON
形式で行うと文字列の比較になるので、よりシンプルに実現できます。
そして、同じくデータのコピーを作成するのも一旦JSON
形式にして、さらに元に戻しています。
なぜなら、そのまま変数に入れてしまうと「同じもの」とされてしまい、params
が変更された場合、同じくoriginalParams
の中身まで変更されてしまうからです。(つまり、この場合ですと常にdirty
はfalse
を返すようになってしまいます)
あとは、save()
で再び現在のparams
をコピーしてoriginalParams
として状態をリセットします。
お疲れ様でした!
ソースコードをダウンロードする
以下から今回開発したソースコード一式をダウンロードすることができます。
Vue
やBootstrap
もcdn
から呼び出しているのですぐ確認することができます!
おわりに
ということで、今回はVue
を使ってページ離脱を防ぐ方法を紹介しました。
ちなみに私は業務管理のウェブシステムを請け負うことが結構あるのですが、ユーザビリティとして「保存忘れ」をなくすためににこのテクニックを使うこともあります。
なお、たまにデータ入力するものは一切ないのに、ページ離脱しようとした瞬間に「ホントにいいの!?損しちゃうよ??」みたいなカンジで引き止めに入ろうとする場合がありますが、あれってちょっと印象悪かったりしますよね
なので、やっぱり何でも適材適所なのかなと思ったりもします。
ぜひ皆さんも適材適所で今回のテクニックを活用してみてくださいね。
ではではー!