Crea una interfaz de búsqueda con el widget de búsqueda

El widget de búsqueda proporciona una interfaz de búsqueda que se puede personalizar para aplicaciones web. El widget solo requiere una pequeña cantidad de código HTML y JavaScript para implementarse, y habilita funciones de búsqueda comunes como facetas y paginación. También puedes personalizar partes de la interfaz con CSS y JavaScript.

Si necesitas más flexibilidad de la que ofrece el widget, considera usar la API de consulta. Para obtener información sobre cómo crear una interfaz de búsqueda con la API de consulta, consulta Crea una interfaz de búsqueda con la API de consulta.

Compila una interfaz de búsqueda

Para compilar una interfaz de búsqueda, debes seguir varios pasos:

  1. Configura una aplicación de búsqueda.
  2. Genera un ID de cliente para la aplicación.
  3. Agrega el lenguaje de marcado HTML para el cuadro de búsqueda y los resultados.
  4. Carga el widget en la página.
  5. Inicializa el widget.

Configura una aplicación de búsqueda

Cada interfaz de búsqueda debe tener una aplicación de búsqueda definida en la Consola del administrador. La aplicación de búsqueda proporciona información adicional para la consulta, como las fuentes de datos, las facetas y la configuración de calidad de la búsqueda.

Para crear una aplicación de búsqueda, consulta Crea una experiencia de búsqueda personalizada.

Genera un ID de cliente para la aplicación

Además de los pasos que se indican en Configura el acceso a la API de Google Cloud Search, también debes generar un ID de cliente para la aplicación web.

Configura un proyecto

Cuando configures el proyecto, realiza los siguientes pasos:

  • Selecciona el tipo de cliente del navegador web.
  • Proporciona la URI de origen de tu aplicación.
  • Toma nota del ID de cliente que se creó. Necesitarás este ID para completar los próximos pasos. No es necesario el secreto del cliente para el widget.

Si deseas obtener información adicional, consulta OAuth 2.0 para la aplicación web del lado del cliente.

Agrega el lenguaje de marcado HTML

El widget requiere una pequeña cantidad de código HTML para funcionar. Debes proporcionar la siguiente información:

  • Un elemento input para el cuadro de búsqueda
  • Un elemento en el cual fijar la ventana emergente de sugerencias.
  • Un elemento para contener los resultados de la búsqueda.
  • Un elemento para contener los controles de facetas (opcional).

En el siguiente fragmento de HTML, se muestra el código HTML para un widget de búsqueda, en el que los elementos que se vincularán se identifican por su 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>

Carga el widget

El widget se carga de manera dinámica través de una secuencia de comandos de cargador. Para incluir el cargador, usa la etiqueta <script> como se muestra a continuación:

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>

Debes proporcionar una devolución de llamada onload en la etiqueta de la secuencia de comandos. La función recibe una llamada cuando el cargador está listo. Cuando el cargador esté listo, continúa con la carga del widget. Para ello, llama a gapi.load() a fin de cargar el cliente de API, el Acceso con Google y los módulos de 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)
}

Se llama a la función initializeApp() después de que se cargan todos los módulos.

Inicializa el widget

Primero, inicializa la biblioteca cliente llamando a gapi.client.init() o gapi.auth2.init() con tu ID de cliente generado y el alcance https://www.googleapis.com/auth/cloud_search.query. Luego, usa las clases gapi.cloudsearch.widget.resultscontainer.Builder y gapi.cloudsearch.widget.searchbox.Builder para configurar el widget y vincularlo a tus elementos HTML.

En el siguiente ejemplo, se muestra cómo inicializar el 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'
    });
  });
}

En el ejemplo anterior, se hace referencia a dos variables para la configuración que se definen de la siguiente manera:

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

Personaliza la experiencia de acceso

De forma predeterminada, el widget solicita a los usuarios acceder y autorizar la aplicación en el momento en que comienzan a escribir una consulta. Puedes usar el Acceso con Google para sitios web a fin de ofrecer una experiencia de acceso más personalizada a los usuarios.

Autoriza a los usuarios directamente

Usa Acceder con Google para supervisar el estado de acceso del usuario y permitir que los usuarios accedan o salgan según sea necesario. En el siguiente ejemplo, se observa el estado isSignedIn para supervisar los cambios de acceso y se usa el método GoogleAuth.signIn() para iniciar el acceso con un clic en un botón:

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 obtener detalles adicionales, consulta Acceder con Google.

Usuarios con acceso automático

Puedes optimizar aún más la experiencia de acceso mediante la autorización previa de la aplicación en nombre de los usuarios de tu organización. Esta técnica también es útil si se usa Cloud Identity Aware Proxy para proteger la aplicación.

Para obtener información adicional, consulta Usa el acceso a Google en aplicaciones de TI.

Personaliza la interfaz

Puedes cambiar la apariencia de la interfaz de búsqueda mediante una combinación de técnicas:

  • Anular los estilos con CSS
  • Decorar los elementos con un adaptador
  • Crear elementos personalizados con un adaptador

Anula los estilos con CSS

El widget de búsqueda cuenta con su propio CSS para diseñar elementos de sugerencias, resultados y controles de paginación. Puedes volver a diseñar estos elementos según sea necesario.

Durante la carga, el widget de búsqueda carga de manera dinámica su hoja de estilo predeterminada. Esto sucede después de que se cargan las hojas de estilo de la aplicación, lo que prioriza las reglas. Para garantizar que tus propios estilos tengan prioridad sobre los estilos predeterminados, usa los selectores principales para aumentar la especificidad de las reglas predeterminadas.

Por ejemplo, la siguiente regla no tiene efecto si se carga en una etiqueta link o style estática en el documento.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

En su lugar, califica la regla con el ID o la clase del contenedor principal declarado en la página.

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

Para ver una lista de las clases de compatibilidad y un ejemplo de código HTML que proporciona el widget, consulta la referencia Clases de CSS compatibles.

Decora los elementos con un adaptador

Para decorar un elemento antes de la renderización, crea y vuelve a registrar un adaptador que implemente uno de los métodos de decoración, como decorateSuggestionElement o decorateSearchResultElement..

Por ejemplo, el siguiente adaptador agrega una clase personalizada a los elementos de sugerencias y resultados.

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 el adaptador cuando inicialices el widget, usa el método setAdapter() de la clase Builder correspondiente:

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

Los decoradores pueden modificar los atributos del elemento del contenedor y cualquier elemento secundario. Los elementos secundarios se pueden agregar o quitar durante la decoración. Sin embargo, si realizas cambios estructurales a los elementos, debes considerar la posibilidad de crear elementos directamente en vez de decorarlos.

Crea elementos personalizados con un adaptador

Si quieres crear un elemento personalizado para una sugerencia, un contenedor de facetas o un resultado de la búsqueda, crea y registra un adaptador que implemente createSuggestionElement, createFacetResultElement o createSearchResultElement, respectivamente.

En los siguientes adaptadores, se muestra cómo crear elementos personalizados de resultados de la búsqueda y sugerencias con etiquetas 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 el adaptador cuando inicialices el widget, usa el método setAdapter() de la clase Builder correspondiente:

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

La creación de elementos personalizados de faceta con createFacetResultElement está sujeta a varias restricciones:

  • Debes adjuntar la clase de CSS cloudsearch_facet_bucket_clickable al elemento en el que los usuarios hacen clic para activar o desactivar un bucket.
  • Debes unir cada bucket a un elemento contenedor con la clase de CSS cloudsearch_facet_bucket_container.
  • No puedes procesar los depósitos en un orden diferente del que aparecen en la respuesta.

Por ejemplo, el siguiente fragmento procesa facetas mediante vínculos, en vez de casillas de verificación.

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

Personaliza el comportamiento de la búsqueda

La configuración de la aplicación de búsqueda representa la configuración predeterminada de una interfaz de búsqueda, y es estática. Para implementar filtros o facetas dinámicos, como permitir a los usuarios activar o desactivar las fuentes de datos, puedes anular la configuración de la aplicación de búsqueda interceptando la solicitud de búsqueda con un adaptador.

Implementa un adaptador con el método interceptSearchRequest para modificar las solicitudes realizadas a la API de búsqueda antes de la ejecución.

Por ejemplo, el siguiente adaptador intercepta las solicitudes para restringir las consultas a una fuente que selecciona el usuario:

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 el adaptador cuando inicialices el widget, usa el método setAdapter() cuando compiles el 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();

El siguiente código HTML se usa a fin de mostrar un cuadro de selección para filtrar por fuentes:

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>

El siguiente código escucha el cambio, establece la selección y vuelve a ejecutar la consulta si es necesario.

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

También puedes interceptar la respuesta de búsqueda mediante la implementación de interceptSearchResponse en el adaptador.

Fija la versión de la API

De forma predeterminada, el widget usa la última versión estable de la API. Para bloquear una versión específica, establece el parámetro de configuración cloudsearch.config/apiVersion en la versión preferida antes de inicializar el widget.

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

La versión predeterminada de la API será 1.0 si se configura o no como un valor no válido.

Fija la versión del widget

Para evitar cambios inesperados en las interfaces de búsqueda, establece el parámetro de configuración cloudsearch.config/clientVersion como se muestra a continuación:

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

La versión predeterminada del widget será 1.0 si se deja sin configurar o se configura como un valor no válido.

Asegura la interfaz de búsqueda

Los resultados de la búsqueda contienen información altamente sensible. Sigue las recomendaciones para proteger las aplicaciones web, especialmente contra ataques de clickjacking.

Para obtener más información, consulta el proyecto guía de OWASP.

Cómo habilitar la depuración

Usa interceptSearchRequest para activar la depuración del widget de búsqueda. Por ejemplo:

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

  return request;