Créer une interface de recherche avec le widget Recherche

Le widget Recherche propose une interface de recherche personnalisable pour les applications Web. L'implémentation du widget ne nécessite qu'une petite quantité de code HTML et JavaScript, et des fonctionnalités de recherche courantes telles que les attributs et la pagination sont activées. Vous pouvez également personnaliser certaines parties de l'interface avec du code CSS et JavaScript.

Si vous avez besoin de plus de flexibilité que le widget, envisagez d'utiliser l'API Query. Pour savoir comment créer une interface de recherche avec l'API Query, consultez l'article Créer une interface de recherche avec l'API Query.

Créer une interface de recherche

Pour créer l'interface de recherche, vous devez suivre plusieurs étapes:

  1. Configurer une application de recherche
  2. Générer un ID client pour l'application
  3. Ajouter le balisage HTML pour le champ de recherche et les résultats
  4. Charger le widget sur la page
  5. Initialiser le widget

Configurer une application de recherche

Chaque interface de recherche doit disposer d'une application de recherche définie dans la console d'administration. L'application de recherche fournit des informations supplémentaires pour la requête, telles que les sources de données, les attributs et les paramètres de qualité de la recherche.

Pour créer une application de recherche, consultez Créer une expérience de recherche personnalisée.

Générer un ID client pour l'application

Outre les étapes de la section Configurer l'accès à l'API Google Cloud Search, vous devez également générer un ID client pour l'application Web.

Configurer un projet

Lorsque vous configurez le projet:

  • Sélectionnez le type de client Navigateur Web
  • Indiquez l'URI d'origine de votre application.
  • Note de l'ID client qui a été créé. Vous en aurez besoin pour effectuer les étapes suivantes. Le code secret du client n'est pas nécessaire pour le widget.

Pour en savoir plus, consultez OAuth 2.0 pour l'application Web côté client.

Ajouter un balisage HTML

Le widget requiert une petite quantité de code HTML pour fonctionner. Vous devez fournir les informations suivantes:

  • Un élément input pour le champ de recherche.
  • Un élément auquel ancrer le pop-up de suggestion
  • Élément contenant les résultats de recherche.
  • (Facultatif) Fournissez un élément qui contiendra les commandes des attributs.

L'extrait de code HTML suivant montre le code HTML d'un widget de recherche, dans lequel les éléments à lier sont identifiés par leur attribut 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>

Charger le widget

Le widget est chargé dynamiquement via un script de chargeur. Pour inclure le chargeur, utilisez la balise <script>, comme indiqué ci-dessous:

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>

Vous devez fournir un rappel onload dans le tag de script. La fonction est appelée lorsque le chargeur est prêt. Lorsque le chargeur est prêt, continuez à charger le widget en appelant gapi.load() pour charger le client API, Google Sign-In et les modules Cloud Search.

diffuser/widget/public/avec_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 fonction initializeApp() est appelée après le chargement de tous les modules.

Initialiser le widget

Commencez par initialiser la bibliothèque cliente en appelant gapi.client.init() ou gapi.auth2.init() avec l'ID client généré et le champ d'application https://www.googleapis.com/auth/cloud_search.query. Utilisez ensuite les classes gapi.cloudsearch.widget.resultscontainer.Builder et gapi.cloudsearch.widget.searchbox.Builder pour configurer le widget et le lier à vos éléments HTML.

L'exemple suivant montre comment initialiser le widget:

diffuser/widget/public/avec_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'
    });
  });
}

L'exemple ci-dessus fait référence à deux variables de configuration définies comme suit:

diffuser/widget/public/avec_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/...";

Personnaliser l'expérience de connexion

Par défaut, le widget invite les utilisateurs à se connecter et à autoriser l'application au moment où ils commencent à saisir une requête. Vous pouvez utiliser Google Sign-In pour les sites Web pour offrir aux utilisateurs une expérience de connexion plus personnalisée.

Autoriser directement les utilisateurs

Utilisez Se connecter avec Google pour surveiller l'état de la connexion de l'utilisateur, et la connexion ou la déconnexion si nécessaire. Par exemple, l'exemple suivant observe l'état isSignedIn pour surveiller les modifications de connexion et utilise la méthode GoogleAuth.signIn() pour initier la connexion à partir d'un bouton.

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

Pour en savoir plus, consultez Se connecter avec Google.

Connecter automatiquement les utilisateurs

Vous pouvez simplifier l'expérience de connexion en pré-autorisant l'application pour le compte des utilisateurs de votre organisation. Cette technique est également utile si vous utilisez Cloud Identity Aware Proxy pour protéger l'application.

Pour en savoir plus, consultez Utiliser Google Sign-In avec des applications informatiques.

Personnaliser l'interface

Vous pouvez modifier l'apparence de l'interface de recherche à l'aide d'une combinaison de techniques:

  • Remplacer les styles par du code CSS
  • Décorer les éléments avec un adaptateur
  • Créer des éléments personnalisés avec un adaptateur

Remplacer les styles par du code CSS

Le widget Recherche est fourni avec son propre CSS pour appliquer un style aux éléments de suggestion et de résultat, ainsi qu'aux commandes de pagination. Vous pouvez modifier le style de ces éléments selon vos besoins.

Lors du chargement, le widget Recherche charge de façon dynamique sa feuille de style par défaut. Cela se produit après le chargement des feuilles de style de l'application, ce qui augmente la priorité des règles. Pour vous assurer que vos propres styles ont priorité sur les styles par défaut, utilisez les sélecteurs d'ancêtre pour augmenter la spécificité des règles par défaut.

Par exemple, la règle suivante n'a aucun effet en cas de chargement dans une balise link ou style statique du document.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

Qualifiez plutôt la règle avec l'ID ou la classe du conteneur d'ancêtre déclaré sur la page.

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

Pour obtenir la liste des classes compatibles et un exemple de code HTML généré par le widget, consultez la documentation de référence sur les classes CSS compatibles.

Décorer les éléments avec un adaptateur

Pour décorer un élément avant le rendu, créez et intégrez un adaptateur qui implémente l'une des méthodes de décoration telles que decorateSuggestionElement ou decorateSearchResultElement..

Par exemple, les adaptateurs suivants ajoutent une classe personnalisée aux éléments de suggestion et de résultat.

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

Pour enregistrer l'adaptateur lors de l'initialisation du widget, utilisez la méthode setAdapter() de la classe Builder correspondante:

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

Les décorateurs peuvent modifier les attributs de l'élément conteneur, ainsi que les éléments enfants. Des éléments enfants peuvent être ajoutés ou supprimés lors de la décoration. Toutefois, si vous apportez des modifications structurelles aux éléments, envisagez de les créer directement au lieu de les décorer.

Créer des éléments personnalisés avec un adaptateur

Pour créer un élément personnalisé pour une suggestion, un conteneur d'attributs ou un résultat de recherche, créez et enregistrez un adaptateur qui implémente createSuggestionElement, createFacetResultElement ou createSearchResultElement de manière répétée.

Les adaptateurs suivants illustrent la création d'éléments de suggestion et de résultats de recherche personnalisés à l'aide de balises HTML <template>.

diffuser/widget/public/avec_élément_personnalisé/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;
}

Pour enregistrer l'adaptateur lors de l'initialisation du widget, utilisez la méthode setAdapter() de la classe Builder correspondante:

diffuser/widget/public/avec_élément_personnalisé/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 création d'éléments d'attribut personnalisés avec createFacetResultElement est soumise à plusieurs restrictions:

  • Vous devez associer la classe CSS cloudsearch_facet_bucket_clickable à l'élément sur lequel les utilisateurs cliquent pour activer/désactiver un bucket.
  • Vous devez encapsuler chaque bucket dans un élément conteneur avec la classe CSS cloudsearch_facet_bucket_container.
  • Vous ne pouvez pas afficher les buckets dans un ordre différent de celui affiché dans la réponse.

Par exemple, l'extrait de code suivant affiche les attributs à l'aide de liens au lieu de cases à cocher.

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

Personnaliser le comportement de recherche

Les paramètres d'application de recherche représentent la configuration par défaut d'une interface de recherche et sont statiques. Pour implémenter des filtres ou des attributs dynamiques, comme permettre aux utilisateurs d'activer/de désactiver des sources de données, vous pouvez ignorer les paramètres de l'application de recherche en interceptant la requête de recherche avec un adaptateur.

Implémentez un adaptateur avec la méthode interceptSearchRequest pour modifier les requêtes adressées à l'API Search avant l'exécution.

Par exemple, l'adaptateur suivant intercepte les requêtes pour limiter les requêtes à une source sélectionnée par l'utilisateur:

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

Pour enregistrer l'adaptateur lors de l'initialisation du widget, utilisez la méthode setAdapter() lorsque vous créez le 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();

Le code HTML suivant permet d'afficher une zone de sélection pour le filtrage par sources:

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>

Le code suivant écoute la modification, définit la sélection et réexécute la requête si nécessaire.

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

Vous pouvez également intercepter la réponse de recherche en implémentant interceptSearchResponse dans l'adaptateur.

Épingler la version de l'API

Par défaut, le widget utilise la dernière version stable de l'API. Pour verrouiller une version spécifique, définissez le paramètre de configuration cloudsearch.config/apiVersion sur la version souhaitée avant d'initialiser le widget.

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

La version de l'API sera définie par défaut sur 1.0 si elle n'est pas configurée ou si elle est définie sur une valeur non valide.

Épingler la version du widget

Pour éviter toute modification inattendue des interfaces de recherche, définissez le paramètre de configuration cloudsearch.config/clientVersion comme indiqué ci-dessous:

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

La version du widget sera définie par défaut sur 1.0 si elle n'est pas configurée ou si elle est définie sur une valeur non valide.

Sécuriser l'interface de recherche

Les résultats de recherche contiennent des informations hautement sensibles. Suivez les bonnes pratiques pour sécuriser les applications Web, en particulier contre les attaques par détournement de clic.

Pour en savoir plus, consultez le guide du guide OWASP.

Activer le débogage

Utilisez interceptSearchRequest afin d'activer le débogage du widget Recherche. Exemple :

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

  return request;