九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
最近ではホントに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の和が広がればうれしいです!
ではでは(^^)