使用搜索微件创建搜索界面

搜索微件为 Web 应用提供可自定义的搜索界面。 它只需要少量 HTML 和 JavaScript 即可实现,并支持构面和分页等常见功能。您还可以使用 CSS 和 JavaScript 自定义界面。

如果需要更灵活地查询,请使用 Query API。请参阅使用 Query API 创建搜索界面

构建搜索界面

构建搜索界面需要执行以下步骤:

  1. 配置搜索应用。
  2. 为应用生成客户端 ID。
  3. 为搜索框和结果添加 HTML 标记。
  4. 在页面上加载微件。
  5. 初始化 widget。

配置搜索应用

每个搜索界面都需要在管理控制台中定义搜索应用。应用提供查询设置,例如数据源、构面和搜索质量参数。

如需创建搜索应用,请参阅打造自定义搜索体验

为应用生成客户端 ID

除了执行配置对 Cloud Search API 的访问权限中介绍的步骤以外,您还必须为 Web 应用生成客户端 ID。

配置项目

配置项目时,请执行以下操作:

  • 选择网络浏览器客户端类型。
  • 提供应用的原始 URI
  • 记下客户端 ID。该 widget 不需要客户端密钥。

如需了解详情,请参阅适用于客户端 Web 应用的 OAuth 2.0

添加 HTML 标记

该 widget 需要以下 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)
}

初始化微件

使用 gapi.client.init()gapi.auth2.init() 初始化客户端库,并提供您的客户端 ID 和 https://www.googleapis.com/auth/cloud_search.query 范围。使用 builder 类来配置和绑定 widget。

初始化示例:

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。请参阅将 Google 登录与 IT 应用配合使用

自定义界面

您可以通过以下方式更改 widget 的外观:

  • 使用 CSS 替换样式。
  • 使用适配器修饰元素。
  • 使用适配器创建自定义元素。

使用 CSS 替换样式

该 widget 包含自己的 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 以构建自定义界面组件。此示例使用 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
  • widget 版本:使用 gapi.config.update('cloudsearch.config/clientVersion', 1.1)

如果未设置,两者均默认为 1.0。

例如,如需将 widget 固定到版本 1.1,请执行以下操作:

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

保护搜索界面

请遵循 Web 应用安全最佳实践,尤其要防止点击劫持

启用调试功能

使用 interceptSearchRequest 启用调试:

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