前回の続き
www.tohuandkonsome.site
さて投げやりになってしまった前回だけど、気をとりなおしていくよ!
前回は、
Vue.jsのフォーム入力バインディング(v-model)を利用してaxiosでサーバーにpostする
ところまでやりました。
javascriptはこんな感じでしたね。
app.js
const app = new Vue({
el: '.simple-form',
data:{
title:'',
body:'',
},
methods:{
onSubmit:function(){
axios.post('/thread', this.$data)
.then(response => console.log(response.data))
.catch(error => console.log(error.response));
},
}
});
それでは、この次に、エラーが発生したときにフォームのコントロールにエラーがでるようにしてあげましょう。
フォームコントロールにエラーを表示する
エラーを格納するクラスErrorsをつくろう
さて、エラーのチェックですが、ベーシックにbody
とtitle
の必須チェックを行います。
また、チェックはサーバ側で行います。
Laravelでバリデーションチェックをすると、以下のようなレスポンスを返してくれます。
Laravelがバリデーションエラーで返してくれるレスポンス
{
"errors": {
"body": [
"本文が未入力だよ"
],
"title": [
"タイトルが未入力だよ"
]
},
"message": "The given data was invalid."
}
なので、このレスポンスを格納するErrorsクラスをつくってみましょう。
javascriptでもクラスをつくれるんだなぁという語弊のある感想はさておき、以下のようなerrorsプロパティをもつクラスをつくってあげます。
また、エラーを設定するセッター的なメソッドrecord
も用意しています。
app.js(抜粋)
class Errors{
constructor() {
this.errors = {}
}
record(errors){
this.errors = errors
}
}
クラスなのでerrors.jsをつくって管理すべきな気もしますが、面倒なのでここではapp.jsにそのまま書いちゃいます。
一方のVueインスタンスでは以下のようにErrorクラスをインスタンス化しておいてdataに用意しておきます。
app.js(抜粋)
const app = new Vue({
el: '.simple-form',
data:{
title:'',
body:'',
errors: new Errors()
},
そうしたら、エラーが発生したときにErrorインスタンスにさきほどのレスポンスをつっこむようにしてあげましょう。
app.js(抜粋)
methods:{
onSubmit:function(){
axios.post('/thread', this.$data)
.then(response => console.log(response.data))
.catch(error => {
this.errors.record(error.response.data.errors);
});
axiosのレスポンスデータの取得の仕方は、errors.response.data
ですが、ここではさらにその配下のネストである、errors.response.data.errors
のほうが何かと都合がいいので、こうしています。
さて、エラーが設定できたので、これをhtml側から参照できるようにしてあげましょう。
create_ajxa.blade.php(抜粋)
<form class="simple-form" action="/thread" method="post" v-on:submit.prevent="onSubmit">
{{csrf_field()}}
<div class="simple-form__group">
<label class="simple-form__title" for="title">タイトル</label>
<input class="simple-form__input" type="text" name="title" v-model="title">
//エラーの情報を表示する
<p class="simple-form__error" v-if="errors.has('title')" v-text="errors.get('title')"></p>
上記のように、errros.get()'でエラーメッセージを取得してあげます。
また、表示する際の条件として
errors.has()`も追加しています。
実装の方は以下の通りです。
app.js(抜粋)
class Errors{
constructor() {
console.log("im created")
this.errors = {}
}
record(errors){
this.errors = errors
}
get(field){
if(this.errors[field]){
return this.errors[field][0];
}
}
has(field){
return this.errors.hasOwnProperty(field)
}
get
メソッドですが、jsonを取得する際に、this.errors[filed]とするのか、this.errors.fieldどっちが正しいのかと混乱しました。
結論としては、オブジェクトのプロパティにアクセスする場合であればどっちでもいいみたいです。
とはいえ、上記のようにプロパティ名が変数になっている場合、配列のようにthis.errors[field]としないとだめみたいですが。
参考にさせていただいた記事
JavaScriptのオブジェクトのキーに変数の値を使うTips - Qiita
もう一方のhas
メソッドですが、こちらも大事な役割も担っています。
hasOwnProperty
はすべてのオブジェクトが持っているメソッドで、オブジェクト内に引数で指定されたプロパティが存在するかをチェックする機能をもっています。
当初、axiosから設定するエラーデータを、errors.response.data
としていて、Errorクラス内部でthis.errors.errros.hasOwnProperty
とやっていました。
しかし、これだと、 エラーが発生していない場合、this.errors.errorsは存在しておらず、hasOwnProperty
なんてないよ!と怒られてしまいます。
あたりまえのことですが、いろいろはまってしまいました。
ここまできたら、試しにエラーが表示されるか試してみます。
何も入力されていない状態で、postをすると、以下のようにエラーが表示されました。
いい感じです。
テキストボックスに値を入力したらエラーを消すようにする
エラーを表示することができたので、今度はエラーを消していきます。
消すタイミングは、タイトルの通りテキストボックスに値が入力されたら消していきたいと思います。
イメージとしては、以下のようにコントロールにキー入力されたらというイベントを@keydown
で捉えてあげます。
そして実行するメソッドはerrors.clear()
になります。
create_ajxa.blade.php(抜粋)
<div class="simple-form__group">
<label class="simple-form__title" for="title">タイトル</label>
<input class="simple-form__input" type="text" name="title" v-model="title" @keydown="errors.clear('title')">
</div>
Errorsクラスはシンプルにdelete
演算子を使って、プロパティを削除してしまいます。
app.js(抜粋)
class Errors{
clear(field){
delete this.errors[field];
}
}
これだけでも、やりたいことは実現できました。
ですが、以下のようにフォームの@keydownイベントして書いてあげると、コントロールごとに記載しなくてもいいのでさらにシンプルになります。
create_ajxa.blade.php(抜粋)
<!--Submitのデフォルトイベントをキャンセルして、VueインスタンスのonSubmitメソッドを呼ぶ-->
<form class="simple-form" action="/thread" method="post" v-on:submit.prevent="onSubmit" @keydown="errors.clear($event.target.name)">
フォームクラスをつくろう
ここまでの内容で、以下のことができるようになりました。
- ブラウザで入力した値をaxiosでサーバーにpostする
- サーバから返却されたエラーメッセージをコントロールにひも付けて表示する
- コントロールに入力すことでエラーメッセージを消す
とはいえ、現時点で気になる点があります。
Vueインスタンスの、データを送信する部分ですがthis.$data
を送っています。
$data
にはtitle
、body
以外にもerrorsも含まれてしまっていてちょっと気持ちが悪いです。
app.js(抜粋)
data:{
title:'',
body:'',
errors: new Errors()
},
methods:{
onSubmit:function(){
axios.post('/thread', this.$data)
なので、ここらでタイトル通りのフォームクラスを作っていきたいと思います。
app.js(抜粋)
class Form{
constructor(data) {
this.originalData = data;
this.errors = new Errors();
}
}
Form
クラスにはerros
プロパティとフォーム内のコントロール要素とバインディングされるデータoriginagData
プロパティを持ちます。
Vueインスタンス側では、こんな感じにしてあげます。
app.js(抜粋)
const app = new Vue({
el: '.simple-form',
data:{
form: new Form({
title:'',
body:'',
})
},
こうすることで、VueインスタンスがFormクラスをもっていて、Formの中にはtitle
やbody
というコントロールがあってというなんとなくオブジェクト指向っぽくなってきた気がしませんか!(頭が悪い)
この方針で、コードを修正していきたいと思います。
フォームクラスに合わせていろいろと修正する
フォームクラスを作った弊害として、html側ではデータをバインディングする際に以下のようにv-model="form.originalData.title"
としてあげなければいけません。
create_ajxa.blade.php(抜粋)
<div class="simple-form__group">
<label class="simple-form__title" for="title">タイトル</label>
<input class="simple-form__input" type="text" name="title" v-model="form.originalData.title">
</div>
ですが、さすがのLaracastさんです。
以下のようにすることで、Formクラスのプロパティとして扱えるようになります。
app.js(抜粋)
class Form{
constructor(data) {
this.originalData = data;
for(let field in this.originalData){
this[field] = this.originalData[field];
}
これで、以下のようにform.title
という形でアクセスできるようになりました。
create_ajxa.blade.php(抜粋)
<div class="simple-form__group">
<label class="simple-form__title" for="title">タイトル</label>
<input class="simple-form__input" type="text" name="title" v-model="form.title">
</div>
また、formに書いたエラーをクリアする処理も、Errorsクラスはフォームクラスのプロパティになったので、以下のように修正します。
create_ajxa.blade.php(抜粋)
<form class="simple-form" action="/thread" method="post" v-on:submit.prevent="onSubmit" @keydown="form.errors.clear($event.target.name)">
エラーを表示する部分も同様に修正しておきます。
create_ajxa.blade.php(抜粋)
<div class="simple-form__group">
<p class="simple-form__error" v-if="form.errors.has('title')" v-text="form.errors.get('title')"></p>
</div>
フォームクラスに機能を移管する
次に、フォームの内容を送信する機能もフォームクラスに移しちゃいましょう!
下記のように、submit
メソッドを追加してあげます。
app.js(抜粋)
class Form{
submit(){
for(let field in this.originalData){
this.originalData[field] = this[field];
}
axios.post('/thread', this.originalData)
.then(response => this.onSuccess(response.data))
.catch(error => this.onFail(error.response.data.errors));
}
onSuccess(response){
console.log(response);
}
onFail(error){
this.errors.record(error);
}
}
基本的に、Vueインスタンスに書いてあった処理をまるっともってくるだけです。
axiosで送信するデータのoriginalData
はコントロールにバインディングされていないので、keyだけあって、値は空っぽです。
なので送信する前に、データバインディングしてあるフォームクラスのプロパティの値を設定してあげています。
そろそろ長くなってきたので次回に回します!