はじめに
前回の記事でLaravel+Vueを使い、API経由で投稿一覧表示部分を実装しました。
EC2のLaravel6.0環境でVue+Rest APIを連携する AWS/Laravel連載(10)
今回はAPIで新規作成(Create)部分を実装します。
controllerの実装
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
return Post::create($request->all());
}
...
これでリクエストパラメータを元にレコード生成するロジック部分は完成です。
バリデーションはあとで実装します。
jsの実装
const app = new Vue({
el: '#app',
data: {
posts: [],
title: '',
content: '',
},
methods: {
fetchPosts: function(){
axios.get('/api/posts').then((res)=>{
this.posts = res.data
})
},
onSubmit: function(){
const params = {
title: this.title,
content: this.content,
};
axios.post('/api/posts', params).then(res =>{
this.title = '';
this.content = '';
this.fetchPosts();
})
}
},
created() {
this.fetchPosts()
},
});
修正したら、忘れずビルドしましょう。
$ npm run dev
viewの実装
...
<form>
<div class="form-group">
<label for="title">タイトル</label>
<input type="text" id="title" v-model="title" class="form-control">
</div>
<div class="form-group">
<label for="content">本文</label>
<textarea v-model="content" id="content" class="form-control"></textarea>
</div>
<input type="button" class="btn btn-primary center" @click="onSubmit()" value="投稿">
</form>
...
ボタンをクリックするとVueのonSubmitメソッドが呼び出されます。
するとv-modelで指定していたタイトル、本文の値がpostされます。
ここまで実装すると、
- フォームに値を入れる
- 送信ボタンを押す
- フォームの値がLaravel側のstoreに飛び、create処理が走ってレコード生成される
- fetchPostsメソッドが走り、今投稿されたデータも含め一覧再取得
- 投稿一覧情報が更新される
となり、レコード生成部分ができあがりました。
バリデーションの実装
しかしバリデーションが存在しません。
タイトル・本文が空欄でもレコードが生成されてしまいます。
空欄の場合に「タイトルを入力してください」といったエラー文言を出したいと思います。
まずはサーバサイドでバリデーションを実装しましょう。
controller側でバリデーション実装
...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$validatedData = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
return Post::create($request->all());
}
...
requiredと入れることで必須要素となります。
$request->validateメソッドを通すことで、エラーがあった場合にその場で例外が返され、Post::createが走らなくなります。
また、max:255は最大255文字までの入力を受け付けるバリデーションです。
複数条件は|で繋いで指定します。
js側でバリデーションエラー時の処理を実装
const app = new Vue({
el: '#app',
data: {
posts: [],
title: '',
content: '',
errors: {},
},
methods: {
fetchPosts: function(){
axios.get('/api/posts').then((res)=>{
this.posts = res.data
})
},
onSubmit: function(){
const params = {
title: this.title,
content: this.content,
};
this.errors = {};
axios.post('/api/posts', params).then(res =>{
this.title = '';
this.content = '';
this.fetchPosts();
}).catch(err =>{
for(var key in err.response.data.errors) {
this.$set(this.errors, key, err.response.data.errors[key].join('<br>'));
}
});
}
},
created() {
this.fetchPosts()
},
});
コツは2点。
1点目は、axiosでcatchをしていること。
これにより、エラーが発生した時の処理を記述しています。
2点目は、
this.$set(this.errors, key, err.response.data.errors[key].join('<br>'));
Vueの配列・オブジェクトは要素を代入してもリアクティブに変更検知ができません。
つまり
this.errors[key] = err.response.data.errors[key].join('<br>')
のように書いても、Vue側でthis.errorsの変更を検知できず、view側でリアルタイムに反映されません。
this.$setを使うことで、オブジェクトの変更を検知し反映されるようにしています。
修正したら、忘れずビルドしましょう。
$ npm run dev
view側でバリデーションエラー時の処理を実装
...
<form>
<div class="form-group">
<label for="title">タイトル</label>
<input type="text" id="title" v-model="title" class="form-control" v-bind:class="{ 'is-invalid': errors.title }">
<p class="alert alert-danger" v-if="errors.title" v-html="errors.title"></p>
</div>
<div class="form-group">
<label for="content">本文</label>
<textarea v-model="content" id="content" class="form-control" v-bind:class="{ 'is-invalid': errors.content }"></textarea>
<p class="alert alert-danger" v-if="errors.content" v-html="errors.content"></p>
</div>
<input type="button" class="btn btn-primary center" @click="onSubmit()" value="投稿">
</form>
...
コツは2点。
v-bind:classとv-ifです。
1点目はv-bindですが、errors.titleが存在した場合にis-invalidというクラスが振られるようになっています。
axiosで例外が投げられた場合にerrors.titleに値が入ってくることで、タイトルにエラーがある時だけis-invalidが振られてフォームパーツが赤色になります。
2点目はv-ifです。
これも似たような実装ですが、errors.titleに値がある時のみ、pタグ要素が表示されます。
そしてエラー文言がpタグの中身に入ります(v-html="errors.title")。
エラーメッセージを日本語化する
基本的には連載6回目の記事でほぼすべて日本語化されているはずです。
ただし唯一項目名のみ英語のはずです。
下記ファイルにこの設定で解決します。
...
'attributes' => [
...
'title' => '題名',
'content' => '本文',
],
...