Crear una interfaz de búsqueda con el widget de la Búsqueda

El widget de la Búsqueda proporciona una interfaz de búsqueda personalizable que se puede usar en aplicaciones web. Este widget, que solo requiere una pequeña cantidad de HTML y JavaScript para implementarse, permite usar funciones de búsqueda comunes, como facetas y paginación. También puedes personalizar algunos elementos de la interfaz con CSS y JavaScript.

Si necesitas más flexibilidad de la que ofrece el widget, puedes usar la API Query. Para obtener más información, consulta el capítulo Crear una interfaz de búsqueda con la API Query.

Crear una interfaz de búsqueda

Para crear una interfaz de búsqueda, debes llevar a cabo estos pasos:

  • Configurar una aplicación de búsqueda.
  • Generar un ID de cliente para asignarlo a la aplicación.
  • Añadir etiquetas HTML para aplicarlas al cuadro de búsqueda y los resultados.
  • Cargar el widget en la página.
  • Inicializar el widget.

Configurar una aplicación de búsqueda

Todas las interfaces de búsqueda deben tener una aplicación de búsqueda definida en la consola de administración. En este aplicación se ofrece información adicional para la consulta, como las fuentes de datos, las facetas y la configuración de calidad de la búsqueda.

Para obtener más información sobre las aplicaciones de búsqueda, consulta el artículo Crear una experiencia de búsqueda personalizada.

Generar un ID de cliente para asignarlo a la aplicación

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

Configurar un proyecto

Para configurar el proyecto, deberás llevar a cabo estas acciones:

  • Seleccionar el tipo de cliente Navegador web.
  • Proporcionar el URI de origen de tu aplicación.
  • Anotar el ID de cliente que se ha creado. Lo necesitarás para completar los siguientes pasos. El secreto de cliente no hace falta para crear el widget.

Para obtener más información, consulta el capítulo sobre OAuth 2.0 para aplicaciones web de cliente.

Añadir etiquetas HTML

El widget requiere una pequeña cantidad de HTML para funcionar. Debes proporcionar estos elementos:

  • Un elemento input para el cuadro de búsqueda
  • Un elemento al que se anclará la ventana emergente de sugerencias
  • Un elemento que contendrá los resultados de búsqueda
  • (Opcional) Un elemento que contendrá los controles de facetas

En el siguiente fragmento de código HTML se muestra el código de un widget de búsqueda, donde los elementos que se van a asociar 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>

Cargar el widget

El widget se carga dinámicamente a través de una secuencia de comandos del 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 retrollamada onload en la etiqueta de la secuencia de comandos. La llamada a la función se realiza cuando el cargador está listo. En ese momento, debes cargar el widget mediante una llamada a gapi.load() para cargar el cliente API, el inicio de sesión de 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)
}

La llamada a la función initializeApp() se realiza una vez que se han cargado todos los módulos.

Inicializar el widget

Primero, inicializa la biblioteca de cliente realizando una llamada a gapi.client.init() o gapi.auth2.init() con tu ID de cliente que se ha generado y el ámbito https://www.googleapis.com/auth/cloud_search.query. A continuación, 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 ejemplo siguiente 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 en la configuración que se definen de esta forma:

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 la experiencia de inicio de sesión

De forma predeterminada, el widget solicita a los usuarios que inicien sesión y autoricen la aplicación en el momento en que comienzan a escribir una consulta. Con el inicio de sesión de Google para sitios web puedes ofrecer a los usuarios una experiencia de inicio de sesión más personalizada.

Autorizar a los usuarios directamente

Con la biblioteca del inicio de sesión de Google para sitios web puedes supervisar el estado de inicio de sesión de los usuarios e iniciar o cerrar su sesión cuando sea necesario. En el siguiente ejemplo se observa el estado isSignedIn para supervisar los cambios de inicio de sesión y se usa el método GoogleAuth.signIn() para iniciar la sesión de usuario mediante un clic de 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 más información, consulta el artículo sobre cómo integrar el inicio de sesión de Google en una aplicación web.

Inicio de sesión automático

Puedes simplificar aún más la experiencia de inicio de sesión autorizando previamente la aplicación en nombre de los usuarios de tu organización. Esta técnica también es útil si usas Cloud Identity-Aware Proxy para proteger la aplicación.

Para obtener más información, consulta el artículo sobre cómo usar el inicio de sesión de Google con aplicaciones de TI.

Personalizar 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.

Anular los estilos con CSS

El widget de la Búsqueda lleva integrado su propio CSS para aplicar estilos a los elementos de sugerencias y resultados, así como controles de paginación. Puedes cambiar el estilo de estos elementos para ajustarlo a tus necesidades.

El widget de la Búsqueda carga dinámicamente su hoja de estilo predeterminada. Esto ocurre una vez que se han cargado las hojas de estilo de la aplicación, lo que aumenta la prioridad de las reglas. Si quieres asegurarte de que tus estilos tengan prioridad sobre los estilos predeterminados, usa los selectores de antecedentes para que las reglas predeterminadas sean lo más precisas posible.

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

.cloudsearch_suggestion_container {
  font-size: 14px;
}

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

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

Para ver una lista de las clases admitidas y un ejemplo de HTML producido por el widget, consulta la referencia clases de CSS admitidas.

Decorar los elementos con un adaptador

Para decorar un elemento antes de renderizarlo, crea y registra un adaptador que implemente uno de los métodos de decoración, como decorateSuggestionElement o decorateSearchResultElement.

Por ejemplo, los siguientes adaptadores añaden 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 al inicializar 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 contenedor, así como cualquier otro elemento secundario. Durante el proceso de decoración, se pueden añadir o quitar elementos secundarios. Sin embargo, si realizas cambios estructurales en los elementos, te aconsejamos que crees los elementos directamente en lugar de decorarlos.

Crear elementos personalizados con un adaptador

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

En los siguientes adaptadores se muestra cómo crear sugerencias personalizadas y elementos de resultados de búsqueda utilizando etiquetas <template> HTML.

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 al inicializar 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 de faceta personalizados con createFacetResultElement tiene algunas limitaciones:

  • Debes adjuntar la clase CSS cloudsearch_facet_bucket_clickable al elemento en que los usuarios hacen clic para cambiar de un segmento a otro.
  • Debes envolver cada segmento en un elemento contenedor con la clase CSS cloudsearch_facet_bucket_container.
  • No puedes procesar los segmentos en un orden diferente al que aparecen en la respuesta.

Por ejemplo, el siguiente fragmento de código procesa las facetas mediante enlaces en lugar 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) {
  // ...
}

Personalizar el comportamiento de búsqueda

Los ajustes de la aplicación de búsqueda representan la configuración predeterminada de una interfaz de búsqueda y son estáticos. Para implementar facetas o filtros dinámicos, como permitir que los usuarios cambien de una fuente de datos a otra, 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 seleccionada por 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 al inicializar el widget, usa el método setAdapter() cuando crees el elemento 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 HTML permite 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 detecta el cambio, define la selección y vuelve a ejecutar la consulta, si fuera 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 implementando interceptSearchResponse en el adaptador.

Fijar 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, define 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');

Proteger la interfaz de búsqueda

Los resultados de búsqueda contienen información estrictamente sensible. Sigue las prácticas recomendadas para proteger aplicaciones web, particularmente contra ataques de clickjacking.

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