Criar uma interface de pesquisa com o widget de pesquisa

O widget da Pesquisa fornece uma interface de pesquisa personalizada para aplicativos da Web. O widget requer apenas uma pequena quantidade de HTML e JavaScript para realizar a implementação e habilita recursos de pesquisa comuns, como atributos e paginação. Também é possível personalizar partes da interface com CSS e JavaScript.

Se você precisar de mais flexibilidade do que a oferecida pelo widget, use a API Query. Para informações sobre como criar uma interface de pesquisa com a API Query, consulte esta página.

Criar uma interface de pesquisa

A criação da interface de pesquisa requer várias etapas:

  • Configurar um app de pesquisa
  • Gerar um ID do cliente para o app
  • Adicionar marcação HTML à caixa de pesquisa e aos resultados
  • Carregar o widget na página
  • Inicializar o widget

Configurar um aplicativo de pesquisa

Cada interface de pesquisa precisa ter um aplicativo de pesquisa definido no Admin Console. O aplicativo de pesquisa fornece mais informações para a consulta, como as origens de dados, os atributos e as configurações de qualidade de pesquisa.

Para mais informações sobre aplicativos de pesquisa, consulte Criar uma experiência de pesquisa personalizada.

Gerar um ID de cliente para o aplicativo

Além das etapas em Configurar o acesso à API REST do Google Cloud Search, também é preciso gerar um ID de cliente para o aplicativo da Web.

Configure um projeto

Ao configurar o projeto:

  • Selecione o tipo de cliente do navegador da Web
  • Forneça o URI de origem (em inglês) do seu aplicativo.
  • Anote o ID de cliente que foi criado. Você precisará dele para concluir as próximas etapas. A chave secreta do cliente não é necessária para o widget.

Para mais informações, consulte OAuth 2.0 para aplicativos da Web do lado do cliente.

Adicionar marcação HTML

O widget requer uma pequena quantidade de HTML para funcionar. Forneça:

  • um elemento input para a caixa de pesquisa;
  • um elemento para ancorar a sugestão pop-up;
  • um elemento para conter os resultados da pesquisa;
  • (opcional) um elemento para conter os controles de atributos.

O snippet HTML a seguir mostra o HTML de um widget de pesquisa, em que os elementos a serem vinculados são identificados pelo atributo 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>

Carregar o widget

O widget é carregado dinamicamente por meio do script do carregador. Para incluir o carregador, use a tag <script>, conforme mostrado:

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>

Forneça um callback onload na tag do script. A função é chamada quando o carregador está pronto. Quando isso acontecer, continue a carregar o widget chamando gapi.load() para carregar os módulos de cliente da API, do Login do Google e do 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)
    }

A função initializeApp() é chamada depois que todos os módulos são carregados.

Inicializar o widget

Primeiro, inicialize a biblioteca de cliente chamando gapi.client.init() ou gapi.auth2.init() com seu ID de cliente gerado e o escopo https://www.googleapis.com/auth/cloud_search.query. Em seguida, use as classes gapi.cloudsearch.widget.resultscontainer.Builder e gapi.cloudsearch.widget.searchbox.Builder para configurar o widget e vinculá-lo aos elementos HTML.

No exemplo a seguir, mostramos como inicializar o 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'
        });
      });
    }

O exemplo acima se refere a duas variáveis para configuração definidas como:

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/...";

Personalizar a experiência de login

Por padrão, o widget solicita que os usuários façam login e autorizem o aplicativo no momento em que começam a digitar uma consulta. Use o Login do Google para sites para oferecer uma experiência de login mais personalizada para os usuários.

Autorizar usuários diretamente

Use a biblioteca do Login do Google para sites para monitorar o estado de login e fazer login ou logout dos usuários conforme necessário. Por exemplo, a amostra a seguir observa o estado isSignedIn para monitorar as alterações de login e usa o método GoogleAuth.signIn() para iniciar o login com o clique em um botão:

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

Para mais detalhes, consulte Como integrar o Login do Google no seu app da Web.

Usuários com login automático

Para simplificar ainda mais a experiência de login, pré-autorize o aplicativo em nome dos usuários da sua organização. Essa técnica também é útil se você usar o Cloud Identity-Aware Proxy para proteger o aplicativo.

Para mais informações, consulte Usar o Login do Google com aplicativos de TI.

Personalizar a interface

É possível alterar a aparência da interface de pesquisa com uma combinação de técnicas:

  • Substituir os estilos por CSS
  • Decorar os elementos com um adaptador.
  • Criar elementos personalizados com um adaptador.

Substituir os estilos por CSS

O widget da Pesquisa vem com o próprio CSS para estilizar elementos de sugestão e de resultados, bem como os controles de paginação. É possível alterar o estilo desses elementos conforme necessário.

Durante o carregamento, o widget de pesquisa carrega dinamicamente sua folha de estilo padrão. Isso ocorre depois que as folhas de estilo do aplicativo são carregadas, aumentando a prioridade das regras. Para garantir que seus próprios estilos tenham precedência sobre os estilos padrão, use seletores de ancestral para aumentar a especificidade das regras padrão.

Por exemplo, a regra a seguir não tem efeito se carregada em uma tag estática link ou style no documento.

.cloudsearch_suggestion_container {
      font-size: 14px;
    }
    

Em vez disso, qualifique a regra com o ID ou a classe do contêiner de ancestral declarado na página.

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

Para ver uma lista de classes compatíveis e exemplos de HTML produzidos pelo widget, consulte a referência Classes CSS compatíveis.

Decorar os elementos com um adaptador

Para decorar um elemento antes de renderizar, crie e registre um adaptador que implemente um dos métodos de decoração, como decorateSuggestionElement ou decorateSearchResultElement..

Por exemplo, os adaptadores a seguir adicionam uma classe personalizada aos elementos sugestão e resultado.

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

Para registrar o adaptador ao inicializar o widget, use o método setAdapter() da respectiva classe 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();

Os decoradores podem modificar os atributos do elemento de contêiner, bem como quaisquer elementos filhos. Elementos filho podem ser adicionados ou removidos durante a decoração. No entanto, se você for fazer alterações estruturais nos elementos, é melhor criá-los diretamente em vez de decorar.

Criar elementos personalizados com um adaptador

Para criar um elemento personalizado para uma sugestão, contêiner de atributo ou resultado de pesquisa, crie e registre um adaptador que implemente createSuggestionElement, createFacetResultElement ou createSearchResultElement respectivamente.

Os adaptadores a seguir ilustram a criação de elementos personalizados de resultados de sugestão e pesquisa usando tags 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;
    }

Para registrar o adaptador ao inicializar o widget, use o método setAdapter() da respectiva classe 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();

A criação de elementos de atributos personalizados com createFacetResultElement está sujeita a várias restrições:

  • É preciso anexar a classe CSS cloudsearch_facet_bucket_clickable ao elemento em que os usuários clicam para alternar um intervalo.
  • É preciso unir cada intervalo em um elemento com a classe CSS cloudsearch_facet_bucket_container.
  • Não é possível renderizar os intervalos em uma ordem diferente da usada para exibi-los na resposta.

Por exemplo, o snippet a seguir renderiza atributos usando links em vez de caixas de seleção.

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) {
      // ...
    }

Personalizar o comportamento da pesquisa

As configurações de aplicativo de pesquisa são estáticas e representam a configuração padrão de uma interface de pesquisa. Para implementar filtros ou atributos dinâmicos, como, por exemplo, permitir que os usuários alternem as origens de dados, é possível substituir as configurações do aplicativo de pesquisa interceptando a solicitação de pesquisa com um adaptador.

Implemente um adaptador com o método interceptSearchRequest para modificar solicitações feitas à API search antes da execução.

Por exemplo, o seguinte adaptador intercepta solicitações para restringir consultas a uma origem selecionada pelo usuário:

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

Para registrar o adaptador ao inicializar o widget, use o método setAdapter() ao criar 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();

O HTML a seguir é usado para exibir uma caixa de seleção para filtragem por origens:

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>

O código a seguir escuta a alteração, define a seleção e executa a consulta novamente, se necessário.

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

É possível também interceptar a resposta da pesquisa implementando interceptSearchResponse no adaptador.

Fixar a versão da API

Por padrão, o widget usa a versão estável mais recente da API. Para manter uma versão específica, defina o parâmetro de configuração cloudsearch.config/apiVersion para a versão preferida antes de inicializar o widget.

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

A versão da API será definida como 1.0 por padrão caso não seja configurada ou tenha um valor inválido.

Fixar a versão do widget

Para evitar alterações inesperadas nas interfaces de pesquisa, defina o parâmetro de configuração cloudsearch.config/clientVersion conforme mostrado:

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

A versão do widget será definida como 1.0 por padrão caso não seja configurada ou tenha um valor inválido.

Proteja a interface de pesquisa

Os resultados da pesquisa contêm informações altamente confidenciais. Siga as práticas recomendadas para proteger aplicativos da Web, especialmente contra ataques de clickjacking (em inglês).

Para mais informações, consulte o Projeto guia OWASP (em inglês).

Ativar a depuração

Use interceptSearchRequest para ativar a depuração do widget de pesquisa. Por exemplo:

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

      return request;