ユーザーが検索ボックスへの入力を開始したときにオートコンプリート (先行入力クエリと提案された結果) を実装する方法について説明します。 このチュートリアルでは、オートコンプリートされたクエリと提案された結果を個別に示し、一緒に表示します。 ユーザーは、使用可能なすべての結果を検索するために、2 文字または 3 文字のみを入力する必要があります。
このチュートリアルでは、以下の内容を学習します。
- 検索候補を追加する
- 候補に強調表示を追加する
- オートコンプリートを追加する
- オートコンプリートと提案を組み合わせる
概要
このチュートリアルでは、前の検索結果にページングを追加するチュートリアルに、オートコンプリート機能と提案結果を追加します。
このチュートリアルのコードの完成版は、次のプロジェクトにあります。
[前提条件]
- 2a-add-paging (GitHub) ソリューション。 このプロジェクトは、前のチュートリアルからビルドされた独自のバージョンか、GitHub からのコピーのいずれかになります。
検索候補を追加する
まず、ユーザーに代替手段を提供する最も簡単なケース(提案された結果のドロップダウン リスト)から始めましょう。
index.cshtml ファイルで、TextBoxFor ステートメントの
@idを azureautosuggest に変更します。@Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautosuggest" }) <input value="" class="searchBoxSubmit" type="submit">このステートメントの後に、終了 </div>の後に、このスクリプトを入力します。 このスクリプトでは、オープンソースの jQuery UI ライブラリの オートコンプリート ウィジェット を利用して、提案された結果のドロップダウン リストを表示します。
<script> $("#azureautosuggest").autocomplete({ source: "/Home/SuggestAsync?highlights=false&fuzzy=false", minLength: 2, position: { my: "left top", at: "left-23 bottom+10" } }); </script>ID
"azureautosuggest"は、上記のスクリプトを検索ボックスに接続します。 ウィジェットのソース オプションは Suggest メソッドに設定され、このインスタンスでは 強調表示 と あいまいの 2 つのクエリ パラメーターを使用して Suggest API を呼び出します。どちらも false に設定されています。 また、検索をトリガーするには、少なくとも 2 文字が必要です。
jQuery スクリプトへの参照をビューに追加する
jQuery ライブラリにアクセスするには、ビュー ファイルの <head> セクションを次のコードに変更します。
<head> <meta charset="utf-8"> <title>Typeahead</title> <link href="https://code.jquery.com/ui/1.12.1/themes/start/jquery-ui.css" rel="stylesheet"> <script src="https://code.jquery.com/jquery-1.10.2.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <link rel="stylesheet" href="~/css/hotels.css" /> </head>新しい jQuery 参照が導入されているため、_Layout.cshtml ファイル ( Views/Shared フォルダー内) の既定の jQuery 参照を削除またはコメント アウトする必要もあります。 次の行を見つけ、次のように最初のスクリプト行をコメント アウトします。 この変更により、jQuery への参照の競合を回避できます。
<environment include="Development"> <!-- <script src="~/lib/jquery/dist/jquery.js"></script> --> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> </environment>これで、定義済みのオートコンプリート jQuery 関数を使用できるようになりました。
コントローラーに Suggest アクションを追加する
ホーム コントローラーで、 (PageAsync アクションの後に) SuggestAsync アクションを追加します。
public async Task<ActionResult> SuggestAsync(bool highlights, bool fuzzy, string term) { InitSearch(); // Setup the suggest parameters. var options = new SuggestOptions() { UseFuzzyMatching = fuzzy, Size = 8, }; if (highlights) { options.HighlightPreTag = "<b>"; options.HighlightPostTag = "</b>"; } // Only one suggester can be specified per index. It is defined in the index schema. // The name of the suggester is set when the suggester is specified by other API calls. // The suggester for the hotel database is called "sg", and simply searches the hotel name. var suggestResult = await _searchClient.SuggestAsync<Hotel>(term, "sg", options).ConfigureAwait(false); // Convert the suggested query results to a list that can be displayed in the client. List<string> suggestions = suggestResult.Value.Results.Select(x => x.Text).ToList(); // Return the list of suggestions. return new JsonResult(suggestions); }Size パラメーターは、返す結果の数を指定します (指定しない場合、既定値は 5 です)。 インデックスの作成時に検索インデックスに suggester が指定されます。 Microsoft がホストするサンプル ホテル インデックスでは、suggester 名は "sg" で、 HotelName フィールドでのみ候補の一致を検索します。
あいまい一致を使用すると、"ニア ミス" を最大 1 つの編集距離で出力に含められます。 highlights パラメーターが true に設定されている場合、太字の HTML タグが出力に追加されます。 次のセクションでは、両方のパラメーターを true に設定します。
構文エラーが発生する場合があります。 その場合は、次の 2 つの using ステートメントをファイルの先頭に追加します。
using System.Collections.Generic; using System.Linq;アプリを実行します。 たとえば、「po」と入力すると、さまざまなオプションが表示されますか? 今度は "pa" を試してみてください。
入力する文字は、単語内に含まれるだけでなく、単語を開始 する必要があることに 注意してください。
ビュー スクリプトで 、>fuzzy を true に設定し、アプリをもう一度実行します。 「po」と入力します。 検索では、1 文字が間違っていると見なされていることに注意してください。
関心がある場合は、 Azure Cognitive Search の Lucene クエリ構文 で、あいまい検索で使用されるロジックについて詳しく説明します。
候補に強調表示を追加する
highlights パラメーターを true に設定することで、ユーザーに対する提案の外観を向上させることができます。 ただし、最初にコードをビューに追加して、太字のテキストを表示する必要があります。
ビュー (index.cshtml) で、前に説明した
"azureautosuggest"スクリプトの後に次のスクリプトを追加します。<script> var updateTextbox = function (event, ui) { var result = ui.item.value.replace(/<\/?[^>]+(>|$)/g, ""); $("#azuresuggesthighlights").val(result); return false; }; $("#azuresuggesthighlights").autocomplete({ html: true, source: "/home/suggest?highlights=true&fuzzy=false&", minLength: 2, position: { my: "left top", at: "left-23 bottom+10" }, select: updateTextbox, focus: updateTextbox }).data("ui-autocomplete")._renderItem = function (ul, item) { return $("<li></li>") .data("item.autocomplete", item) .append("<a>" + item.label + "</a>") .appendTo(ul); }; </script>次に、テキスト ボックスの ID を次のように変更します。
@Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azuresuggesthighlights" }) <input value="" class="searchBoxSubmit" type="submit">アプリをもう一度実行すると、入力したテキストが提案に太字で表示されます。 「pa」と入力してみてください。
上記の強調表示スクリプトで使用されているロジックは、確実ではありません。 同じ名前で 2 回表示される用語を入力した場合、太字の結果は望ましい結果ではありません。 「mo」と入力してみてください。
開発者が答える必要がある質問の 1 つは、スクリプトが "十分に機能している" 場合と、その風変わりな点に対処すべきタイミングです。 このチュートリアルではこれ以上強調表示しませんが、正確なアルゴリズムを見つけることは、強調表示がデータに対して有効でない場合に考慮する必要があります。 詳細については、「 ヒットの強調表示」を参照してください。
オートコンプリートを追加する
提案とは少し異なるもう 1 つのバリエーションは、クエリ用語を完了するオートコンプリート ("先行入力" とも呼ばれます) です。 ここでも、ユーザー エクスペリエンスを向上させる前に、最も簡単な実装から始めます。
前のスクリプトに従って、次のスクリプトをビューに入力します。
<script> $("#azureautocompletebasic").autocomplete({ source: "/Home/Autocomplete", minLength: 2, position: { my: "left top", at: "left-23 bottom+10" } }); </script>次にテキストボックスのIDを変更して、次のように表示されるようにします。
@Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautocompletebasic" }) <input value="" class="searchBoxSubmit" type="submit">ホーム コントローラーで、SuggestAsync アクションの後に AutocompleteAsync アクションを入力します。
public async Task<ActionResult> AutoCompleteAsync(string term) { InitSearch(); // Setup the autocomplete parameters. var ap = new AutocompleteOptions() { Mode = AutocompleteMode.OneTermWithContext, Size = 6 }; var autocompleteResult = await _searchClient.AutocompleteAsync(term, "sg", ap).ConfigureAwait(false); // Convert the autocompleteResult results to a list that can be displayed in the client. List<string> autocomplete = autocompleteResult.Value.Results.Select(x => x.Text).ToList(); return new JsonResult(autocomplete); }オートコンプリート検索では、候補の場合と同じ suggester 関数 "sg" を使用していることに注意してください (そのため、ホテル名のオートコンプリートのみを試みている)。
AutocompleteMode にはさまざまな設定があり、OneTermWithContext を使用しています。 その他のオプションの説明については、 オートコンプリート API を参照してください。
アプリを実行します。 ドロップダウン リストに表示されるオプションの範囲が 1 つの単語であることがわかります。 "re" で始まる単語を入力してみてください。 より多くの文字が入力されると、オプションの数がどのように減っているかに注意してください。
現状では、前に実行した候補スクリプトは、このオートコンプリート スクリプトよりも役に立つ可能性があります。 オートコンプリートをより使いやすいものにするには、推奨される結果と共に使用することを検討してください。
オートコンプリートと候補を組み合わせる
オートコンプリートと提案を組み合わせることは、オプションの中で最も複雑であり、おそらく最高のユーザー エクスペリエンスを提供します。 私たちが求めているのは、入力中のテキストにインラインで表示される形で、Azure Cognitive Searchの最初のオートコンプリート候補を示すことです。 また、ドロップダウン リストとしてさまざまな候補が必要です。
この機能を提供するライブラリがあります。多くの場合、"インラインオートコンプリート" または同様の名前と呼ばれます。 ただし、API を探索できるように、この機能をネイティブに実装します。 この例では、最初にコントローラーで作業を開始します。
指定した数の候補と共に、オートコンプリートの結果を 1 つだけ返すアクションをコントローラーに追加します。 このアクションを AutoCompleteAndSuggestAsync と呼びます。 ホーム コントローラーで、他の新しいアクションに従って、次のアクションを追加します。
public async Task<ActionResult> AutoCompleteAndSuggestAsync(string term) { InitSearch(); // Setup the type-ahead search parameters. var ap = new AutocompleteOptions() { Mode = AutocompleteMode.OneTermWithContext, Size = 1, }; var autocompleteResult = await _searchClient.AutocompleteAsync(term, "sg", ap); // Setup the suggest search parameters. var sp = new SuggestOptions() { Size = 8, }; // Only one suggester can be specified per index. The name of the suggester is set when the suggester is specified by other API calls. // The suggester for the hotel database is called "sg" and simply searches the hotel name. var suggestResult = await _searchClient.SuggestAsync<Hotel>(term, "sg", sp).ConfigureAwait(false); // Create an empty list. var results = new List<string>(); if (autocompleteResult.Value.Results.Count > 0) { // Add the top result for type-ahead. results.Add(autocompleteResult.Value.Results[0].Text); } else { // There were no type-ahead suggestions, so add an empty string. results.Add(""); } for (int n = 0; n < suggestResult.Value.Results.Count; n++) { // Now add the suggestions. results.Add(suggestResult.Value.Results[n].Text); } // Return the list. return new JsonResult(results); }結果一覧の上部に 1 つのオートコンプリート オプションが返され、その後にすべての候補が返されます。
ビューでは、まず、ユーザーが入力する太字のテキストのすぐ下に薄い灰色のオートコンプリート ワードがレンダリングされるようにトリックを実装します。 HTML には、この目的のための相対配置が含まれています。 TextBoxFor ステートメント (およびその周囲の <div> ステートメント) を次のように変更します。この検索ボックスを既定の場所から 39 ピクセル引き離すことで、その下に指定された 2 つ目の検索ボックスが通常の検索ボックスのすぐ下にあることを示します。
<div id="underneath" class="searchBox" style="position: relative; left: 0; top: 0"> </div> <div id="searchinput" class="searchBoxForm" style="position: relative; left: 0; top: -39px"> @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautocomplete" }) <input value="" class="searchBoxSubmit" type="submit"> </div>この場合、ID を azureautocomplete に再度変更していることに注意してください。
また、ビューで、これまでに入力したすべてのスクリプトの後に、次のスクリプトを入力します。 スクリプトは、処理するさまざまな入力動作により、長く複雑です。
<script> $('#azureautocomplete').autocomplete({ delay: 500, minLength: 2, position: { my: "left top", at: "left-23 bottom+10" }, // Use Ajax to set up a "success" function. source: function (request, response) { var controllerUrl = "/Home/AutoCompleteAndSuggestAsync?term=" + $("#azureautocomplete").val(); $.ajax({ url: controllerUrl, dataType: "json", success: function (data) { if (data && data.length > 0) { // Show the autocomplete suggestion. document.getElementById("underneath").innerHTML = data[0]; // Remove the top suggestion as it is used for inline autocomplete. var array = new Array(); for (var n = 1; n < data.length; n++) { array[n - 1] = data[n]; } // Show the drop-down list of suggestions. response(array); } else { // Nothing is returned, so clear the autocomplete suggestion. document.getElementById("underneath").innerHTML = ""; } } }); } }); // Complete on TAB. // Clear on ESC. // Clear if backspace to less than 2 characters. // Clear if any arrow key hit as user is navigating the suggestions. $("#azureautocomplete").keydown(function (evt) { var suggestedText = document.getElementById("underneath").innerHTML; if (evt.keyCode === 9 /* TAB */ && suggestedText.length > 0) { $("#azureautocomplete").val(suggestedText); return false; } else if (evt.keyCode === 27 /* ESC */) { document.getElementById("underneath").innerHTML = ""; $("#azureautocomplete").val(""); } else if (evt.keyCode === 8 /* Backspace */) { if ($("#azureautocomplete").val().length < 2) { document.getElementById("underneath").innerHTML = ""; } } else if (evt.keyCode >= 37 && evt.keyCode <= 40 /* Any arrow key */) { document.getElementById("underneath").innerHTML = ""; } }); // Character replace function. function setCharAt(str, index, chr) { if (index > str.length - 1) return str; return str.substr(0, index) + chr + str.substr(index + 1); } // This function is needed to clear the "underneath" text when the user clicks on a suggestion, and to // correct the case of the autocomplete option when it does not match the case of the user input. // The interval function is activated with the input, blur, change, or focus events. $("#azureautocomplete").on("input blur change focus", function (e) { // Set a 2 second interval duration. var intervalDuration = 2000, interval = setInterval(function () { // Compare the autocorrect suggestion with the actual typed string. var inputText = document.getElementById("azureautocomplete").value; var autoText = document.getElementById("underneath").innerHTML; // If the typed string is longer than the suggestion, then clear the suggestion. if (inputText.length > autoText.length) { document.getElementById("underneath").innerHTML = ""; } else { // If the strings match, change the case of the suggestion to match the case of the typed input. if (autoText.toLowerCase().startsWith(inputText.toLowerCase())) { for (var n = 0; n < inputText.length; n++) { autoText = setCharAt(autoText, n, inputText[n]); } document.getElementById("underneath").innerHTML = autoText; } else { // The strings do not match, so clear the suggestion. document.getElementById("underneath").innerHTML = ""; } } // If the element loses focus, stop the interval checking. if (!$input.is(':focus')) clearInterval(interval); }, intervalDuration); }); </script>interval 関数は、ユーザーが入力している内容と一致しなくなった際に基礎テキストをクリアし、また使用者が入力している大文字・小文字と同じ状態に設定するため("pa" が検索時に "PA"、"pA"、"Pa" と一致するため)にどのように用いられているかに注意してください。これにより、オーバーレイされたテキストを整然と表示できます。
スクリプト内のコメントを読んで、理解を深めます。
最後に、2 つの HTML クラスを透過的にするために、小さな調整を行う必要があります。 hotels.css ファイル内の searchBoxForm クラスと searchBox クラスに次の行を追加します。
background: rgba(0,0,0,0);次に、アプリを実行します。 検索ボックスに「pa」と入力します。 オートコンプリートの提案として、「palace」が表示され、「pa」を含む2つのホテルも表示されますか?
タブを使用してオートコンプリート候補を受け入れ、方向キーとタブ キーを使用して候補を選択し、マウスと 1 回のクリックでもう一度やり直してください。 スクリプトがこれらすべての状況を適切に処理することを確認します。
この機能を提供するライブラリに読み込む方が簡単だと判断することもできますが、インラインオートコンプリートを機能させる方法が少なくとも 1 つわかっています。
学んだこと
このプロジェクトの次の点について考えてみましょう。
- オートコンプリート ("先行入力" とも呼ばれます) と提案により、ユーザーは数個のキーのみを入力して、必要なものを正確に見つけることができます。
- オートコンプリートと提案が連携することで、豊富なユーザー エクスペリエンスを実現できます。
- すべての形式の入力でオートコンプリート関数を常にテストします。
- setInterval 関数を使用すると、UI 要素の検証と修正に役立ちます。
次のステップ
次のチュートリアルでは、ファセットを使用して 1 回のクリックで検索を絞り込むという、ユーザー エクスペリエンスを向上させる別の方法について説明します。