検索ウィジェットを使用して検索インターフェースを作成する

検索ウィジェットは、ウェブ アプリケーション用のカスタマイズ可能な検索インターフェースを提供します。実装に必要な HTML と JavaScript は最小限で、ファセットやページ分割などの一般的な機能をサポートしています。CSS や JavaScript を使用してインターフェースをカスタマイズすることもできます。

より柔軟な対応が必要な場合は、Query API を使用します。Query API を使用して検索インターフェースを作成するをご覧ください。

検索インターフェースを作成する

検索インターフェースを作成するには、次の手順が必要です。

  1. 検索アプリケーションを構成します。
  2. アプリケーションのクライアント ID を生成します。
  3. 検索ボックスと結果用の HTML マークアップを追加します。
  4. ページにウィジェットを読み込みます。
  5. ウィジェットを初期化します。

検索アプリケーションを構成する

各検索インターフェースには、管理コンソールで定義された検索アプリケーションが必要です。アプリケーションは、データソース、ファセット、検索品質パラメータなどのクエリ設定を提供します。

検索アプリケーションを作成するには、カスタム検索アプリケーションを作成するをご覧ください。

アプリケーションのクライアント ID を生成する

Cloud Search API へのアクセスを構成するの手順に加えて、ウェブ アプリケーションのクライアント ID を生成します。

プロジェクトを構成する

プロジェクトを構成する際は、次の点に注意してください。

  • [ウェブブラウザ] のクライアント タイプを選択します。
  • アプリの生成元 URI を指定します。
  • クライアント ID をメモします。ウィジェットにはクライアント シークレットは必要ありません。

詳しくは、クライアント側ウェブ アプリケーションの OAuth 2.0 をご覧ください。

HTML マークアップを追加する

ウィジェットには次の HTML 要素が必要です。

  • 検索ボックスの input 要素。
  • 候補ダイアログを固定するための要素。
  • 検索結果の要素。
  • (省略可)ファセット コントロールの要素。

次のスニペットは、id 属性で識別される要素を示しています。

serving/widget/public/with_css/index.html
<div id="search_bar">
  <div id="suggestions_anchor">
    <input type="text" id="search_input" placeholder="Search for...">
  </div>
</div>
<div id="facet_results"></div>
<div id="search_results"></div>

ウィジェットを読み込む

<script> タグを使用してローダーを含めます。

serving/widget/public/with_css/index.html
<!-- Google API loader -->
<script src="https://apis.google.com/js/api.js?mods=enable_cloud_search_widget&onload=onLoad" async defer></script>

onload コールバックを提供します。ローダーの準備ができたら、gapi.load() を呼び出して API クライアント、Google ログイン、Cloud Search の各モジュールを読み込みます。

serving/widget/public/with_css/app.js
/**
* Load the cloud search widget & auth libraries. Runs after
* the initial gapi bootstrap library is ready.
*/
function onLoad() {
  gapi.load('client:auth2:cloudsearch-widget', initializeApp)
}

ウィジェットを初期化する

クライアント ID と https://www.googleapis.com/auth/cloud_search.query スコープを使用して、gapi.client.init() または gapi.auth2.init() を使用してクライアント ライブラリを初期化します。ビルダー クラスを使用してウィジェットを構成し、バインドします。

初期化の例:

serving/widget/public/with_css/app.js
/**
 * Initialize the app after loading the Google API client &
 * Cloud Search widget.
 */
function initializeApp() {
  // Load client ID & search app.
  loadConfiguration().then(function() {
    // Set API version to v1.
    gapi.config.update('cloudsearch.config/apiVersion', 'v1');

    // Build the result container and bind to DOM elements.
    var resultsContainer = new gapi.cloudsearch.widget.resultscontainer.Builder()
      .setSearchApplicationId(searchApplicationName)
      .setSearchResultsContainerElement(document.getElementById('search_results'))
      .setFacetResultsContainerElement(document.getElementById('facet_results'))
      .build();

    // Build the search box and bind to DOM elements.
    var searchBox = new gapi.cloudsearch.widget.searchbox.Builder()
      .setSearchApplicationId(searchApplicationName)
      .setInput(document.getElementById('search_input'))
      .setAnchor(document.getElementById('suggestions_anchor'))
      .setResultsContainer(resultsContainer)
      .build();
  }).then(function() {
    // Init API/oauth client w/client ID.
    return gapi.auth2.init({
        'clientId': clientId,
        'scope': 'https://www.googleapis.com/auth/cloud_search.query'
    });
  });
}

構成変数:

serving/widget/public/with_css/app.js
/**
* Client ID from OAuth credentials.
*/
var clientId = "...apps.googleusercontent.com";

/**
* Full resource name of the search application, such as
* "searchapplications/<your-id>".
*/
var searchApplicationName = "searchapplications/...";

ログイン エクスペリエンスをカスタマイズする

ユーザーが入力を開始すると、ウィジェットにログインを求めるメッセージが表示されます。ウェブサイト用の Google ログインを使用すると、カスタマイズされたログイン方式を提供できます。

ユーザーを直接承認する

Google でログインを使用して、ログイン状態をモニタリングして管理します。この例では、ボタンのクリック時に GoogleAuth.signIn() を使用しています。

serving/widget/public/with_signin/app.js
// Handle sign-in/sign-out.
let auth = gapi.auth2.getAuthInstance();

// Watch for sign in status changes to update the UI appropriately.
let onSignInChanged = (isSignedIn) => {
  // Update UI to switch between signed in/out states
  // ...
}
auth.isSignedIn.listen(onSignInChanged);
onSignInChanged(auth.isSignedIn.get()); // Trigger with current status.

// Connect sign-in/sign-out buttons.
document.getElementById("sign-in").onclick = function(e) {
  auth.signIn();
};
document.getElementById("sign-out").onclick = function(e) {
  auth.signOut();
};

ユーザーを自動的にログインさせる

組織内のユーザーに対してアプリケーションを事前に承認して、ログインを簡素化します。これは Cloud Identity Aware Proxy でも役立ちます。IT アプリで Google ログインを使用するをご覧ください。

インターフェースをカスタマイズする

ウィジェットの外観は、次の方法で変更できます。

  • CSS でスタイルをオーバーライドする。
  • アダプタで要素を修飾する。
  • アダプタを使用してカスタム要素を作成する。

CSS でスタイルをオーバーライドする

ウィジェットには独自の CSS が含まれています。オーバーライドするには、祖先セレクタを使用して特異性を高めます。

#suggestions_anchor .cloudsearch_suggestion_container {
  font-size: 14px;
}

サポートされている CSS クラスのリファレンスをご覧ください。

アダプタで要素を修飾する

レンダリング前に要素を変更するアダプタを作成して登録します。この例では、カスタム CSS クラスを追加します。

serving/widget/public/with_decorated_element/app.js
/**
 * Search box adapter that decorates suggestion elements by
 * adding a custom CSS class.
 */
function SearchBoxAdapter() {}
SearchBoxAdapter.prototype.decorateSuggestionElement = function(element) {
  element.classList.add('my-suggestion');
}

/**
 * Results container adapter that decorates suggestion elements by
 * adding a custom CSS class.
 */
function ResultsContainerAdapter() {}
ResultsContainerAdapter.prototype.decorateSearchResultElement = function(element) {
  element.classList.add('my-result');
}

初期化中にアダプタを登録します。

serving/widget/public/with_decorated_element/app.js
// Build the result container and bind to DOM elements.
var resultsContainer = new gapi.cloudsearch.widget.resultscontainer.Builder()
  .setAdapter(new ResultsContainerAdapter())
  // ...
  .build();

// Build the search box and bind to DOM elements.
var searchBox = new gapi.cloudsearch.widget.searchbox.Builder()
  .setAdapter(new SearchBoxAdapter())
  // ...
  .build();

アダプタを使用してカスタム要素を作成する

createSuggestionElementcreateFacetResultElementcreateSearchResultElement を実装して、カスタム UI コンポーネントを構築します。この例では、HTML の <template> タグを使用しています。

serving/widget/public/with_custom_element/app.js
/**
 * Search box adapter that overrides creation of suggestion elements.
 */
function SearchBoxAdapter() {}
SearchBoxAdapter.prototype.createSuggestionElement = function(suggestion) {
  let template = document.querySelector('#suggestion_template');
  let fragment = document.importNode(template.content, true);
  fragment.querySelector('.suggested_query').textContent = suggestion.suggestedQuery;
  return fragment.firstElementChild;
}

/**
 * Results container adapter that overrides creation of result elements.
 */
function ResultsContainerAdapter() {}
ResultsContainerAdapter.prototype.createSearchResultElement = function(result) {
  let template = document.querySelector('#result_template');
  let fragment = document.importNode(template.content, true);
  fragment.querySelector('.title').textContent = result.title;
  fragment.querySelector('.title').href = result.url;
  let snippetText = result.snippet != null ?
    result.snippet.snippet : '';
  fragment.querySelector('.query_snippet').innerHTML = snippetText;
  return fragment.firstElementChild;
}

アダプターを登録します。

serving/widget/public/with_custom_element/app.js
// Build the result container and bind to DOM elements.
var resultsContainer = new gapi.cloudsearch.widget.resultscontainer.Builder()
  .setAdapter(new ResultsContainerAdapter())
  // ...
  .build();

// Build the search box and bind to DOM elements.
var searchBox = new gapi.cloudsearch.widget.searchbox.Builder()
  .setAdapter(new SearchBoxAdapter())
  // ...
  .build();

カスタム ファセット要素は、次のルールに従う必要があります。

  • クリック可能な要素に cloudsearch_facet_bucket_clickable を追加します。
  • 各バケットを cloudsearch_facet_bucket_container でラップします。
  • レスポンスのバケット順序を維持します。

たとえば、次のスニペットは、チェックボックスの代わりにリンクを使用してファセットをレンダリングします。

serving/widget/public/with_custom_facet/app.js
/**
 * Results container adapter that intercepts requests to dynamically
 * change which sources are enabled based on user selection.
 */
function ResultsContainerAdapter() {
  this.selectedSource = null;
}

ResultsContainerAdapter.prototype.createFacetResultElement = function(result) {
  // container for the facet
  var container = document.createElement('div');

  // Add a label describing the facet (operator/property)
  var label = document.createElement('div')
  label.classList.add('facet_label');
  label.textContent = result.operatorName;
  container.appendChild(label);

  // Add each bucket
  for(var i in result.buckets) {
    var bucket = document.createElement('div');
    bucket.classList.add('cloudsearch_facet_bucket_container');

    // Extract & render value from structured value
    // Note: implementation of renderValue() not shown
    var bucketValue = this.renderValue(result.buckets[i].value)
    var link = document.createElement('a');
    link.classList.add('cloudsearch_facet_bucket_clickable');
    link.textContent = bucketValue;
    bucket.appendChild(link);
    container.appendChild(bucket);
  }
  return container;
}

// Renders a value for user display
ResultsContainerAdapter.prototype.renderValue = function(value) {
  // ...
}

検索動作をカスタマイズする

アダプタでリクエストをインターセプトして、検索アプリケーションの設定をオーバーライドします。interceptSearchRequest を実装して、実行前にリクエストを変更します。この例では、クエリを選択したソースに制限しています。

serving/widget/public/with_request_interceptor/app.js
/**
 * Results container adapter that intercepts requests to dynamically
 * change which sources are enabled based on user selection.
 */
function ResultsContainerAdapter() {
  this.selectedSource = null;
}
ResultsContainerAdapter.prototype.interceptSearchRequest = function(request) {
  if (!this.selectedSource || this.selectedSource == 'ALL') {
    // Everything selected, fall back to sources defined in the search
    // application.
    request.dataSourceRestrictions = null;
  } else {
    // Restrict to a single selected source.
    request.dataSourceRestrictions = [
      {
        source: {
          predefinedSource: this.selectedSource
        }
      }
    ];
  }
  return request;
}

アダプターを登録します。

serving/widget/public/with_request_interceptor/app.js
var resultsContainerAdapter = new ResultsContainerAdapter();
// Build the result container and bind to DOM elements.
var resultsContainer = new gapi.cloudsearch.widget.resultscontainer.Builder()
  .setAdapter(resultsContainerAdapter)
  // ...
  .build();

次の HTML は、ソースでフィルタリングするために選択ボックスを表示する目的で使用されます。

serving/widget/public/with_request_interceptor/index.html
<div>
  <span>Source</span>
  <select id="sources">
    <option value="ALL">All</option>
    <option value="GOOGLE_GMAIL">Gmail</option>
    <option value="GOOGLE_DRIVE">Drive</option>
    <option value="GOOGLE_SITES">Sites</option>
    <option value="GOOGLE_GROUPS">Groups</option>
    <option value="GOOGLE_CALENDAR">Calendar</option>
    <option value="GOOGLE_KEEP">Keep</option>
  </select>
</div>

次のコードは、変更をリッスンし、選択を設定し、必要に応じてクエリを再実行します。

serving/widget/public/with_request_interceptor/app.js
// Handle source selection
document.getElementById('sources').onchange = (e) => {
  resultsContainerAdapter.selectedSource = e.target.value;
  let request = resultsContainer.getCurrentRequest();
  if (request.query) {
    // Re-execute if there's a valid query. The source selection
    // will be applied in the interceptor.
    resultsContainer.resetState();
    resultsContainer.executeRequest(request);
  }
}

アダプタに interceptSearchResponse を実装して、検索レスポンスをインターセプトすることもできます。

バージョンを固定する

  • API バージョン: 初期化する前に cloudsearch.config/apiVersion を設定します。
  • ウィジェットのバージョン: gapi.config.update('cloudsearch.config/clientVersion', 1.1) を使用します。

どちらも設定しない場合、デフォルトは 1.0 です。

たとえば、ウィジェットをバージョン 1.1 に固定するには:

serving/widget/public/basic/app.js
gapi.config.update('cloudsearch.config/apiVersion', 'v1');

検索インターフェースを保護する

ウェブ アプリケーションのセキュリティに関するベスト プラクティスに沿って、特にクリックジャッキングを防止します。

デバッグを有効にする

デバッグを有効にするには、interceptSearchRequest を使用します。

if (!request.requestOptions) {
  request.requestOptions = {};
}
request.requestOptions.debugOptions = {enableDebugging: true};
return request;