
九保すこひです(フリーランスのITコンサルタント、エンジニア)
最近ではホントにAjaxを使ったウェブコンテンツが多くなってきましたね。
個人的にはSPA(Single-page Application)っていうのは、「かっこいいけど複雑すぎて保守に向かないやりかた」という位置づけに落ち着いたんですけど、それでもやっぱりAjaxを使って開発をすると時短になりますし、とても重宝しています。
そこで、今回は開発者として、
今からAjaxを使ってお問い合わせ機能を作れ!
と言われたらどんな流れで開発を進めるのか?を順を追って説明してみたいと思います。
開発環境は、
- PHP: Laravel 5+
- JavaScript: Vue.js, axios
です。では行ってみましょう!
1.土台をつくる
まずは土台となる3つのファイルをつくらないといけません。
この場合、以下の3つですね。
- コントローラー(Controller)
- ルーティング(Routing)
- ビュー(View)
ひとつひとつみていきましょう。
(1)コントローラー
Laravelにはコントローラー・ファイルを自動で作ってくれるコマンドが用意されています。
以下のように作成しましょう。
php artisan make:controller ContactController
※本来ならすでにあるHomeControllerに書くんですけど、今回はテストということで専用コントローラーです。
で、コントローラーが作成されたら以下のように、Viewを設定します。
(Viewはあとでつくります)
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
class ContactController extends Controller
{
public function index() {
return view('contact');
}
}
で、Ajaxを使ってデータを送受信するので、さっきの要領でAjax専用のコントローラーもつくりましょう。(Ajaxの後はバックスラッシュ2連続です)
php artisan make:controller Ajax\\ContactController
中身としては、メール送信になるんですけど、今回は省略しています。
また、レスポンスはjsonで返しています。
<?php
namespace App\Http\Controllers\Ajax;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class ContactController extends Controller
{
public function store() {
// メールを送信する
return response()->json([
'result' => true
]);
}
}
はい、これでコントローラーは完了です!
(2)Route
ルーティングなんですけど、Laravelのバージョンでファイルが違うので、注意が必要です。
Laravel 5.3 以降
/routes/web.php
Laravel 5.2 以前
/app/Http/routes.php
Laravelも日々進化してるんですね。
で、実際の設定は以下のとおりです。
Route::get('/contacts', 'ContactController@index');
Route::group(['prefix' => 'ajax'], function() {
Route::resource('contacts', 'Ajax\ContactController', [
'only' => ['store']
]);
});
2つ目ちょっとがちょっとわかりにくいかもしれませんけど、要するにpostで /ajax/contacts にアクセスするようにしています。シンプルなほうがいい場合は、
Route::get('/contacts', 'ContactController@index');
Route::post('/ajax/contacts', 'Ajax\ContactController@store');
でもOKです。(この辺はRESTfulとかを調べてください)
さぁ、これでルーティングも完了です。
(3)ビュー
ビューは簡単で、さっきコントローラーで設定した場所にファイルをつくるだけです。
(今回はテストなので@extendsなどは使わず、直にHTMLタグを書いてますし、metaタグなどは省略しています。)
Vueとaxiosはcdnを使ってロードし、axiosをvue内で使いやすくするために$httpにセットしています。
※ちなみになぜビューには作成コマンドがないのか不思議です。わざわざこんなパッケージを公開してる人がいるぐらいなんだから、つければいいのに・・・・・・。
/views/contact.blade.php
<html>
<head>
<title>お問い合わせ</title>
</head>
<body>
<div id="app">
<label>名前</label>
<input type="text">
<br>
<label>メールアドレス</label>
<input type="text">
<br>
<label>内容</label>
<textarea></textarea>
<br>
<button type="button">送信する</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
Vue.prototype.$http = axios;
new Vue({
el: '#app'
})
</script>
</body>
</html>
さぁ、これで土台が完了しました!
次からJavaScript部分を見ていくことにしましょう!
Vueを使ったAjax部分
まずはinputにv-modelを使って変数をバインディングしていきましょう。
(JS)
new Vue({
el: '#app',
data: {
params: {
name: '',
email: '',
body: ''
}
}
});
(HTML)
<label>名前</label>
<input type="text" v-model="params.name">
<br>
<label>メールアドレス</label>
<input type="text" v-model="params.email">
<br>
<label>内容</label>
<textarea v-model="params.body"></textarea>
はい!
これで入力ボックスに変更があったら、自動でparamsの中身が書きかわりますし、逆にparamsを変更すれば入力値が自動更新されます。(ツー・ウェイ・データ・バインディングですね。マジ便利!)
そしたら次は送信ボタンがクリックされたときのイベントです。
これもVueなら簡単ですよ!
(JS)
new Vue({
el: '#app',
data: {
params: {
name: '',
email: '',
body: ''
}
},
methods: {
onClick: function() {
// ここにAjax
}
}
});
※そろそろアロー関数とか使うべきなんでしょうけど、例の問題児、IEがまだサポートしてないらしいので、あえてfunctionを使っています(笑)
(HTML)
<button type="button" @click="onClick">送信する</button>
では、次にAjax通信部分です。
これもaxiosなら簡単ですよ。
onClick: function() {
this.$http.post('/ajax/contacts', this.params)
.then(function(response){
// 成功したとき
}).catch(function(error){
// 失敗したとき
});
}
さっき、Vueにaxiosを設定したんで、this.$httpとして呼び出すことができます。
しかも、データ・バインディングでthis.paramsの中には入力値がすべて含まれているんですね。
では、通信が成功したときのコードを見ていきましょう。
アラートを出して、入力値をクリアします。
onClick: function() {
var self = this;
this.$http.post('/ajax/contacts', this.params)
.then(function(response){
// 成功したとき
self.params = {
name: '',
email: '',
body: ''
};
alert('送信が完了しました。');
}).catch(function(error){
// 失敗したとき
});
}
ここで重要なのは「self」の部分です。axiosのthen、catchの中身はコールバック関数なんで、thisだとスコープ違いでデータを参照できないんですね。
では、次に通信に失敗した場合なんですけど、今のところ/ajax/contactsではエラーは発生しません。なので、先にバリデーションを作っていきましょう。
これもLaravelにはいいコマンドがあります。
php artisan make:request ContactRequest
ContactRequestは「app/Http/Requests/ContactRequest.php」にできているのでファイルを開いてバリデーション設定をします。
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class ContactRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required|email',
'body' => 'required'
];
}
}
デフォルトから変更したのは、autorize()のreturnをtrueへ。
それから、rules()内で各種バリデーション・ルールを書き込んでいます。
これが完了したらAjax\ContactControllerに設置しましょう。
こんなカンジです。
<?php
namespace App\Http\Controllers\Ajax;
use Illuminate\Http\Request;
use App\Http\Requests\ContactRequest;
use App\Http\Controllers\Controller;
class ContactController extends Controller
{
public function store(ContactRequest $request) {
// メールを送信する
return response()->json([
'result' => true
]);
}
}
useを使ってネームスペースを解決しているのに注意して下さいね。
これで、$requestを使って入力値を取得できますし、自動でバリデーションも機能します。
※ちなみにバリデーションに失敗した場合はHTTPステータスコードは422が返ってきます。
さぁ、ではエラー内容を入力ボックスに表示してみましょう!
(JS)
new Vue({
el: '#app',
data: {
params: {
name: '',
email: '',
body: ''
},
errors: {
name: '',
email: '',
body: ''
}
},
methods: {
onClick: function() {
var self = this;
this.errors = {
name: '',
email: '',
body: ''
};
this.$http.post('/ajax/contacts', this.params)
.then(function(response){
// 成功したとき
// (コード省略)
}).catch(function(error){
// 失敗したとき
for(var key in error.response.data) {
self.errors[key] = error.response.data[key][0];
}
});
}
}
});
ちょっとコードが多くなっちゃいましたけど、やってることはさっきと一緒で、errorsという変数を用意してあげて、エラー文章をそこへ格納してるだけです。
で、errorsはAjax送信する直前に、必ずerrorsを初期化するようにしています。
つづいてHTMLのエラー表示です。
(HTML)
<label>名前</label>
<input type="text" v-model="params.name">
<div v-if="errors.name">@{{ errors.name }}</div>
<br>
<label>メールアドレス</label>
<input type="text" v-model="params.email">
<div v-if="errors.email">@{{ errors.email }}</div>
<br>
<label>内容</label>
<textarea v-model="params.body"></textarea>
<div v-if="errors.body">@{{ errors.body }}</div>
注目すべきところは「v-if」でエラーが存在してるときは表示、そうじゃなければ表示しないようにしているところです。
さっきも書きましたけど、Vueのバインディングはデータが更新されたらすぐ表示も変更になるので、こんなシンプルな書き方ができるわけです。
ちなみにバリデーション・エラーの日本語化をしたい場合は/resources/lang/jaというフォルダを作りそこにここから持ってきた翻訳データを格納すればOKです。
もし切り替わらない場合は、
app()->setLocale('ja');
などとして言語を変更してください。
そして、このままでは「nameは、必ず指定してください。」という具合にアトリビュートが変数名になってしまうので、validation.php内のattributes項目に以下のように追加してください。
'attributes' => [
'name' => '名前',
'email' => 'メールアドレス',
'body' => '問い合わせ内容'
],
はい!これでエラー部分も完了です。
では、最後にAjax部分の完全版コードを紹介しておしまいにします。
<html>
<head>
<title>お問い合わせ</title>
</head>
<body>
<div id="app">
<label>名前</label>
<input type="text" v-model="params.name">
<div v-if="errors.name">@{{ errors.name }}</div>
<br>
<label>メールアドレス</label>
<input type="text" v-model="params.email">
<div v-if="errors.email">@{{ errors.email }}</div>
<br>
<label>内容</label>
<textarea v-model="params.body"></textarea>
<div v-if="errors.body">@{{ errors.body }}</div>
<br>
<button type="button" @click="onClick">送信する</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
Vue.prototype.$http = axios;
new Vue({
el: '#app',
data: {
params: {},
errors: {}
},
methods: {
initParams: function(){
this.params = {
name: '',
email: '',
body: ''
};
},
initErrors: function(){
this.errors = {
name: '',
email: '',
body: ''
};
},
onClick: function() {
var self = this;
this.initErrors();
this.$http.post('/ajax/contacts', this.params)
.then(function(response){
// 成功したとき
self.initParams();
alert('送信が完了しました。');
}).catch(function(error){
// 失敗したとき
for(var key in error.response.data) {
self.errors[key] = error.response.data[key][0];
}
});
}
},
mounted: function() {
this.initParams();
this.initErrors();
}
});
</script>
</body>
</html>
重複するコードがあったんで、ひとつにまとめてますけど、内容は一緒です。
vueが初めての方に説明が必要だとしたらmountedの部分。
これはページが読み込まれたらすぐに起動する場所です。window.onloadみたいなもんですね。
と、いうことで今回はちょっと長めの記事を書いてみました。
この記事で、Laravelの和が広がればうれしいです!
ではでは(^^)