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

搜索微件为 Web 应用提供可自定义的搜索界面。 该微件只需少量 HTML 和 JavaScript 即可实现 实现并启用常用搜索功能,例如分面和分页。您还可以使用 CSS 和 JavaScript 自定义界面的各个部分。

如果该微件的灵活性未能满足您的需要,请考虑使用 Query API。如需了解如何使用 Query API 创建搜索界面,请参阅使用 Query API 创建搜索界面

构建搜索界面

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

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

配置搜索应用

每个搜索界面都必须在管理控制台中定义搜索应用搜索应用提供了 查询相关信息,例如数据源、分面、 和搜索质量设置

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

为应用生成客户端 ID

除了 配置对 Google Cloud Search API 的访问权限。 您还必须为 Web 应用生成客户端 ID。

配置项目

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

  • 选择网络浏览器客户端类型。
  • 提供应用的原始 URI
  • 记下所创建的客户端 ID。您需要使用客户端 ID 完成后续步骤。该微件不需要用到客户端密钥。

如需了解其他信息,请参阅适用于客户端 Web 应用的 OAuth 2.0

添加 HTML 标记

该微件只需要少量 HTML 即可运行。您必须提供以下元素:

  • 搜索框的 input 元素。
  • 锚定弹出式建议的元素。
  • 包含搜索结果的元素。
  • (可选)包含构面控件的元素。

以下 HTML 代码段显示了搜索微件的 HTML,其中 要绑定的元素由其 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 回调。加载程序准备就绪后,系统会调用该函数。加载器准备就绪后,继续加载该 widget 方法是调用 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)
}

在所有模块完成更新后,系统会调用 initializeApp() 函数 。

初始化微件

首先,通过调用以下代码初始化客户端库 将 gapi.client.init()gapi.auth2.init() 替换为生成的客户端 ID 和 https://www.googleapis.com/auth/cloud_search.query 范围。然后,使用 gapi.cloudsearch.widget.resultscontainer.Builder和 用于配置 widget 的 gapi.cloudsearch.widget.searchbox.Builder 类 并将其绑定到您的 HTML 元素

以下示例显示了如何初始化该微件:

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 账号登录功能来监控 并根据需要登录或退出用户。例如,以下 示例会观察 isSignedIn 状态来监控登录更改 使用 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();
};

如需了解更多详情,请参阅使用 Google 账号登录

自动登录用户

您可以代表您的组织中的用户预先向应用授权,从而进一步简化登录体验。在使用 Cloud Identity-Aware Proxy 保护应用时,此项技术也很有用。

如需了解其他信息,请参阅将 Google 登录与 IT 应用配合使用

自定义界面

您可以通过组合使用以下技术更改搜索界面的外观:

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

使用 CSS 替换样式

搜索微件附带了自己的 CSS,用于为建议和结果元素以及分页控件设置样式。您可以根据需要重新设置这些元素的样式。

在加载期间,搜索微件会动态加载其默认样式表。 该操作发生在应用样式表加载完成后,从而提高优先级 规则的特点如需确保您自己的样式优先于默认样式,请使用祖先选择器提高默认规则的特异性。

例如,在静态 文档中存在 linkstyle 标记。

.cloudsearch_suggestion_container {
  font-size: 14px;
}

请改为使用在页面中声明的祖先容器的 ID 或类来限定规则。

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

如需查看支持类列表和微件生成的示例 HTML,请参阅支持的 CSS 类参考。

使用适配器修饰元素

要在渲染之前装饰元素,请创建并重新注册 适配器,用于实现某种装饰方法(如 decorateSuggestionElementdecorateSearchResultElement.

例如,以下适配器可以将自定义类添加到建议和结果元素中。

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');
}

如需在初始化 widget 时注册适配器,请使用 setAdapter() 方法:Builder

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();

修饰器可以修改容器元素以及任何子元素的特性。在装饰期间,可以添加或移除子元素。 但是,如果要更改元素的结构,请考虑创建 而不是装饰

使用适配器创建自定义元素

如需为建议、分面容器或搜索结果创建自定义元素,请按以下步骤操作: 创建并注册一个适配器 createSuggestionElementcreateFacetResultElement、 或 createSearchResultElement

以下适配器展示了如何创建自定义建议和搜索结果 元素(使用 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;
}

如需在初始化 widget 时注册适配器,请使用 setAdapter() 方法:Builder

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();

使用 createFacetResultElement 创建自定义分面元素 需要遵循以下几项限制:

  • 您必须将 CSS 类 cloudsearch_facet_bucket_clickable 附加到 元素被用户点击以切换存储分区。
  • 您必须将每个存储分区都封装在一个含有 CSS 的包含元素中, 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 方法用于修改向 搜索 API

例如,以下适配器会拦截请求,将查询限制在用户所选的数据源:

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;
}

要在初始化 widget 时注册适配器,请使用 setAdapter() 方法(在构建 ResultsContainer 时)

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 版本

默认情况下,该微件使用最新的稳定版 API。要锁定 请设置 cloudsearch.config/apiVersion 配置参数 首选版本。

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

如果未设置 API 版本或者将其设置为无效值,则默认使用 1.0 版。

锁定微件版本

为避免意外更改搜索界面,请将 cloudsearch.config/clientVersion 配置参数,如下所示:

gapi.config.update('cloudsearch.config/clientVersion', 1.1);

如果未设置微件版本或者将其设置为无效值,则系统将默认使用 1.0 版。

保护搜索界面

搜索结果包含高度敏感的信息。请按照最佳做法来保护 Web 应用,尤其要保护 Web 应用免遭点击劫持攻击。

如需了解更多信息,请参阅 OWASP 指南项目

启用调试功能

使用interceptSearchRequest 来启用搜索微件的调试功能。例如:

  if (!request.requestOptions) {
  // Make sure requestOptions is populated
  request.requestOptions = {};
  }
  // Enable debugging
  request.requestOptions.debugOptions = {enableDebugging: true}

  return request;