WEBシステムで入力フォームを作っている際に必ずと言ってもいいほど必要になるの「AutoComplete(Suggest)」の機能です。
私は今までjQueryを利用してAutoComplete(Suggest)の機能を実装していたのですが、最近Vue.jsを触りはじめたのでそろそろjQueryから脱却したいなと思っていました。
まずHTML5のDatalistを利用することで、比較的簡単に実装することができたため試してみました。
AutoCompleteを自作した理由
先ほどのDatalistを利用した方法だとブラウザや機種などで幾つか制限があります(下のMEMO参照)。
そのため、OSやブラウザ の種類などのシステムの要件によっては使えないパターンも発生してしまうので、Datalist以外の方法でもAutoCompleteを実装でないか考えておりました。
Vue.jsライブラリとして「vue-simple-suggest」や「vue-cool-select」なども試してみたのですが、今回は私自身のVue.jsの学習も兼ねてVue.js+LaravelでAutoCompleteを自作してみることにしました!!
AutoCompleteを作成するには、Vue.jsとJavascriptのイベントなどの動きを理解しないと作れません。Vue.js初心者の学習にもオススメです。
私もVue.jsを触りはじめて2ヶ月程度の初心者ですがかなり理解が深まった気がします。
具体的にはHTML5に対応しているブラウザ以外は動作しません。また、iPhoneの場合にはブラウザ問わず(Safari/Chrome)動作しないとの情報がありますので注意してください。
進め方
今回は学習もかねているため、段階的に機能を追加していきたいと思います。かなり長くなったため全4回に記事を分けて解説していきたいと思います、
以下の流れで作っていきます。
- 第1回:AutoComplete基本部分の作成
- 事前データ準備(テストデータ準備+Ajax用の API作成)
- Vueコンポーネと作成
- Ajaxでデータ取得
- 結果をサジェストリスト表示
- アイテムクリックで選択
- 第2回:サジェストリストのフォーカス表示機能の追加
- 上下などのキーボード操作でのフォーカス移動
- マウスオーバでのフォーカス表示、など
- 第3回:サジェストにマッチした文字の強調表示
- 第4回:Laravelから再利用がしやすいようにする
- Laravelから再利用しやすいようにコンポーネント化する
最終的なページ
最終的に完成したページとしては以下のようになります。
サジェストはもちろんですが、各種キーボードでの操作(上や下などを押した際の動作)や入力文字にマッチした文字の強調まで行ないます。
またLaravelからパーツとして使いやすくするために、VueコンポーネントへLaravelブレードかパラメータ(名前とか)を渡せるようにします。(例えばFormでのnameの値やajaxのurlなど)
もう少し改善できるとは思いますがとりあえずの完成形としています。動画を以下にのせておきます。
AutoComplete基本部分の作成
第1回の完成ページ
はじめに第1回が終わった時に出来上がるページについて確認しておきます。ポイントは以下です。
- フォームに入力された文字列でAPIへ問合せしてサジェストする値を取得
- サジェストされた結果をリアルタイムでリスト表示
- サジェストされたアイテムをクリックすることで選択したアイテムがフォームにセットされる
まだこの段階ではキーボードの上下やEnterを押したりマウスオーバーしてもフォーカス表示などはされません。
テストデータの準備
まずはお決まりの事前に実際にテストで利用するデータを準備します。以下の記事にあるテストデータの準備まで行っておいてください。
こんな感じのダミーデータが入っていればOKです。
Ajax用のAPIを作成
Ajax用のAPIを作成します。/search/emailでアクセスした際にemailで検索したデータをJson形式で最大5件返すものを作成します。
routes/web.phpに以下を追記してルートを追加します。(合わせてページ表示するためのルート追加してます)
// ページ表示用
Route::resource('/emp', 'EmployeeController');
// API用
Route::get('/search/email', 'EmployeeController@search_email')->name('search.email');
app/Http/Controllers/EmployeeController.phpファイルに追加します。
// API用
public function search_email(Request $request)
{
$searchquery = $request->input('query');
$data = Employee::where('email', 'like', '%' . $searchquery . '%')->limit(5)->get();
return response()->json($data);
}
// ページ表示用
public function create()
{
return view('create');
}
実際にURLへアクセスして正しく値が取れるかを確認します。私の環境では以下のURLです。http://localhost:8003/search/email?query=ishi
AutoCompleteコンポーネントの作成
それではいよいよ本題のVueコンポーネントの作成を行っていきます。まずは以下の場所に以下のファイルを作成します。
ファイルは以下の内容を記載します。
resources/js/components/AutoCompleteComponent.vue
<template>
<div>
<input
type="text"
placeholder="what are you looking for?"
v-model="query"
class="form-control"
autocomplete="off"
name="email"
@input="getSuggestionList"
/>
<div class="panel-footer" v-if="results.length && open">
<ul class="list-group">
<li
v-for="(result, index) in results"
v-bind:key="result.index"
class="list-group-item list-group-item-action"
@click="suggestionClick(index)"
v-text="result.email"
></li>
</ul>
</div>
</div>
</template>
<script>
export default {
data: function() {
return {
query: "",
results: [],
open: false,
current: 0
};
},
methods: {
getSuggestionList() {
this.results = [];
if (this.query.length >= 1) {
axios
.get("/search/email", {
params: { query: this.query }
})
.then(Response => {
this.results = Response.data;
if (this.results) {
this.open = true;
}
})
.catch(error => {
console.log(error);
});
}
},
suggestionClick(index) {
this.query = this.results[index].email;
this.open = false;
this.current = 0;
this.results = this.results[index];
}
}
};
</script>
続いて[resources/js/app.js]に作成したファイルパスを登録します。
Vue.component(
"auto-complete-component",
require("./components/AutoCompleteComponent.vue").default
);
Laravel側の準備
Laravelのビューとしてブレードファイルを作成します。/emp/createでページを表示するようにします。
resources/views/create.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Test AutoComplete Vue.js</div>
<div class="card-body">
<form method="POST" action="{{ route('emp.store') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">E-mail</label>
<div class="col-md-6">
<auto-complete-component/>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<input type="text" name="dummy" style="display:none;">
<button type="submit" class="btn btn-primary">
登録
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
動作確認
それでは動作を確認してみましょう。npm run devを実施してからアクセスして下さい。前にあったようにサジェスト表示がされれば成功です。
解説
それでは解説していきます。今回はAjaxでサジェストするデータ取得している部分とサジェスト結果をv-forで表示している部分がポイントになります。
Vueコンポーネントの呼び出し
create.blade.phpファイル内にある以下の行でVueコンポーネントを呼び出ししてます。特にこの時点では呼び出しだけしてます。
<auto-complete-component/>
Ajaxでのデータ取得
続いてAutoCompleteComponent.vue側の解説に進みます。
はじめにAjaxでデータを取得している部分について解説します。これはAxiosを利用したことがある方であれば比較的簡単に理解ができるかと思います。Axiosめちゃ便利です。
- フォームに「何か」が入力されたタイミングでgetSuggestionListメソッドを呼び出し(@input=”getSuggestionList”の部分)
- getSuggestionListメソッドではaxiosを使ってurl(search/email)に対して通信を実施(queryで検索)
- 2文字目の入力で問合せを発動(if (this.query.length > 1)の部分)
- 結果をresultsに格納
サジェストリストをv-forで表示
Ajaxで取得したサジェスト結果をv-forを使って<li>のアイテムとして表示しています。このタイミングでは表示するだけのものです。
current変数については表示を制御するために利用しますが次回説明します。
openでサジェストリストの表示/非表示をコントロール
サジェスト結果のリスト表示についてはopenという変数を用意して制御しています。具体的には以下のような動きです。
- openがTrue、かつ結果(results)がある時にサジェストリストが表示されるようにする
<div class=”panel-footer” v-if=”results.length && open”> - ページを開いた時はリストは開かせたくないのでopen=falseの状態
- サジェスト結果resultsが取得できた時にサはジェストリストを開きたいのでopen=trueへ変更
if (this.results) {
this.open = true;
} - suggestionClickした時にはサジェストリストを閉じたいのでopen=falseへ変更
サジェストリストのアイテムをクリックで値をセット
サジェストリストで表示されたアイテムをクリックした際に結果をqueryへセットします。suggestionClickメソッドの部分です。クリックされた際には以下の動作をします。
- queryにクリックしたアイテム(emailパラメータ)をセット
- resultsへクリックしたアイテム(配列として)をセット
- openはfalseに変更して閉じる
ポイントはresultsのリセットとopenをfalseにしている部分かと思います。これがないとそのままqueryの修正があった時に前回のサジェスト結果が表示されたりしてしまいます。
第1回まとめ
ここまででAutoCompleteで必要となる最低限の機能を作ってきました。これだけでもかなりボリュームがあります。
今回利用したAxiosの通信などは他にも色々な場面で使われているテクニックになりますので是非習得してください。
次回はサジェストリストをフォーカスするなどの表示部分について作っていきたいと思います。
今回は以上となります。
コメント
画面がいちいち動くのをやめて欲しいです。ユーザビリティーが悪いです。記事はすごいいいので画面は動か斎でください
コメントありがとうございます。
確認させていただきたいのですが、画面がいちいち動くというのは埋め込んでいる動画の部分が自動的に動くということでしょうか?
私の環境(Mac or Win+Chrome)では自動再生にはなってないので確認させていただければと思います。