はじめに
検索欄に文字を打つたびに候補がぱっと出てくるあの体験、作ろうとすると意外と面倒です。 入力イベントを拾って、候補データを絞り込んで、DOMを描画して、キーボード操作にも対応して……。 自前で書くとそれなりの量のコードになりますし、既存のUIライブラリを丸ごと入れるのも 大げさに感じることがあります。
そんなときに候補に挙がるのが、Twitter社が開発したTypeahead.jsです。
検索候補を計算するエンジンのBloodhoundと、それを画面に描画するjQueryプラグインの
typeahead。この2つの組み合わせだけで、入力補助のあるテキストボックスがすぐに作れます。
とはいえ、読むより触った方が早いと思います。ローカル配列から候補を絞り込んで表示する サンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Typeaheadとは
Typeahead.jsは、Twitter.comの検索窓の入力補助機能に着想を得て作られた
JavaScriptライブラリです。ライブラリは2つのコンポーネントで構成されています。
候補データの管理と検索を担当するBloodhound(サジェストエンジン)と、
候補をDOMに描画してキーボード・マウス操作を処理するjQuery#typeahead(UIビュー)です。
この2つは別々に使うこともできますが、組み合わせることで本格的な検索候補UIになります。
主な特徴
- ローカルデータとリモートデータの両方に対応 - 固定の配列で完結させることも、 APIから取得したデータをバックフィルすることもできる
- キャッシュとレート制限を内蔵 -
prefetchはlocalStorageにキャッシュし、remoteはリクエストの間引きも行うため、余計な通信を意識せずに済む - 表示のカスタマイズ性が高い -
templatesオプションでヘッダー・フッター・ 候補一つひとつの描画テンプレートを差し替えられる - キーボード操作に標準対応 - 上下キーでの候補選択、Enterでの確定、 ハイライト表示まで最初から組み込まれている
インストール
npmでインストールする場合は次のコマンドを実行します。
npm install typeahead.js jquery
CDNから読み込む場合は、jQueryとtypeahead.jsのバンドル版を<script>タグで
読み込むだけで使えます。Typeahead.jsはjQueryのプラグインとして動作するため、
jQueryを先に読み込んでおく必要がある点に注意してください。
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/typeahead.js@0.11.1/dist/typeahead.bundle.min.js"></script>
Typeaheadのサンプルを動かす
下のサンプルは、Bloodhoundにプログラミング言語名の配列を渡し、
$('#search').typeahead()で入力欄に候補表示を付けたものです。
jaやpyのように一部だけ入力すると、一致した文字がhighlight: trueの効果で
太字表示されるのが分かります。
const languages = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: ['JavaScript', 'TypeScript', 'Python', 'Ruby', 'Go', 'Rust']
})
$('#search').typeahead(
{ hint: true, highlight: true, minLength: 1 },
{ name: 'languages', source: languages, limit: 5 }
)
実際に動かせるサンプルが下です。入力欄に文字を打ち込んで候補を確認してください。
minLengthを1にしているので、1文字目から候補が絞り込まれます。
Bloodhoundのlocal配列を書き換えるだけで候補データは自由に差し替えられます。
limitを3にすれば表示件数が絞られますし、hint: falseにすると入力欄の背景に
薄く表示される補完候補(ヒント文字列)が消えます。挙動を変えながら試してみてください。
基本的な使い方
Typeahead.jsを使う最小構成は、Bloodhoundインスタンスを作ってtypeahead()に
渡すだけです。datumTokenizerとqueryTokenizerは、候補データと入力文字列を
どう単語分割して比較するかを決めるオプションで、空白区切りでよければ
Bloodhound.tokenizers.whitespaceを指定しておけば動きます。
const fruits = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: ['apple', 'banana', 'cherry', 'grape', 'orange']
})
$('#fruit-input').typeahead(
{
hint: true,
highlight: true,
minLength: 1
},
{
name: 'fruits',
source: fruits
}
)
jQuery#typeahead(options, dataset)の第1引数がtypeahead全体の設定、
第2引数以降がデータセットごとの設定です。データセットはname・source・limit・
display・templatesといったオプションを持てるので、1つの入力欄に複数の候補ソースを
グループとして持たせることもできます。
実践的なユースケース
複数データセットをヘッダー付きでグループ表示する
検索候補を「フレームワーク」「言語」のようにカテゴリ分けして見せたい場合は、
typeahead()に複数のデータセットを渡し、それぞれにtemplates.headerを
設定します。1つの入力欄からカテゴリの異なる候補をまとめて検索できるのが、
Typeahead.jsの複数データセット機能の利点です。
$('#search').typeahead(
{ highlight: true, minLength: 1 },
{
name: 'frameworks',
source: frameworks,
templates: { header: '<h4 class="tt-header">フレームワーク</h4>' }
},
{
name: 'languages',
source: languages,
templates: { header: '<h4 class="tt-header">言語</h4>' }
}
)
実際に動かせるサンプルです。reやpyのように入力すると、
フレームワークと言語の両方のグループから候補が絞り込まれて表示されます。
データセットごとにlimitやdisplayを変えれば、カテゴリごとに表示件数や
表示形式を変えることも可能です。ヘッダーのテキストやスタイルを変更すると、
どのデータセットに何件ヒットしたかがより分かりやすくなります。
typeahead:selectイベントで選択結果をタグとして扱う
候補を選んだあと、その値を入力欄に残すのではなく「タグ」として別枠に追加したい
場面もあります。スキル検索やラベル付けのUIがその代表例です。これは
typeahead:selectイベントを使うと実現できます。選択された候補オブジェクトが
イベントハンドラの第2引数で渡されるので、そこでタグを追加し、
typeahead('val', '')で入力欄をクリアします。
$('#tag-input').on('typeahead:select', function (ev, suggestion) {
addTag(suggestion)
$('#tag-input').typeahead('val', '')
})
function addTag(tag) {
const span = document.createElement('span')
span.className = 'tag'
span.textContent = tag
span.onclick = () => span.remove()
document.getElementById('tags').appendChild(span)
}
実際に動かせるサンプルです。候補を選ぶとタグとして下に追加され、 タグをクリックすると削除できます。
addTagの中身を変えれば、削除ボタン付きのチップにしたり、選択済みのタグを
候補から除外したりと、実際のフォームに近づけたカスタマイズがしやすくなります。
まとめ
Typeahead.jsは、候補データの管理を担うBloodhoundと、DOM描画・キーボード操作を
担うjQuery#typeaheadプラグインの組み合わせで、検索候補UIを短いコードで実装できる
ライブラリです。ローカル配列だけの単純な絞り込みから、複数データセットのグループ表示、
typeahead:selectイベントを使った選択後の処理まで、この記事で紹介したパターンを
組み合わせれば、多くの検索窓のニーズはカバーできます。jQueryをすでに使っている
プロジェクトであれば、導入のハードルも低いはずです。ぜひ自分のフォームに組み込んで
試してみてください。
