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

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

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

构建搜索界面

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

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

配置搜索应用

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

如需详细了解搜索应用,请参阅打造自定义搜索体验

为应用生成客户端 ID

除了执行配置对 Google Cloud Search REST 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>

您必须在 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)
    }

加载所有模块后,系统会调用 initializeApp() 函数。

初始化微件

首先,使用生成的客户端 ID 和 https://www.googleapis.com/auth/cloud_search.query 范围调用 gapi.client.init()gapi.auth2.init() 来初始化客户端库。然后使用 gapi.cloudsearch.widget.resultscontainer.Buildergapi.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 登录集成到您的 Web 应用

自动登录用户

您可以代表您组织中的用户向应用预先授权,从而进一步简化登录体验。在使用 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');
    }

如需在初始化微件时注册适配器,请使用相应 Builder 类的 setAdapter() 方法:

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

如需在初始化微件时注册适配器,请使用相应 Builder 类的 setAdapter() 方法:

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 方法实现一个适配器,用于在执行之前先修改对 Search 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;
    }

如需在初始化微件时注册适配器,请在构建 ResultsContainer 时使用 setAdapter() 方法。

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;