Créer une interface de recherche avec le widget Recherche

Le widget Recherche propose une interface de recherche personnalisable pour les applications Web. La mise en œuvre et l'activation des fonctionnalités de recherche courantes, telles que les attributs et la pagination, nécessitent peu de code HTML et JavaScript. Vous pouvez également personnaliser certaines parties de l'interface avec du code CSS et JavaScript.

Si besoin, sachez que l'API Query offre davantage de flexibilité que le widget. Pour savoir comment créer une interface de recherche à l'aide de cette API, consultez l'article Créer une interface de recherche avec l'API Query.

Créer une interface de recherche

La procédure de création d'une interface de recherche comprend les étapes suivantes :

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

Configurer une application de recherche

Une application de recherche doit être définie dans la console d'administration pour chaque interface de recherche. Cette application fournit des informations supplémentaires nécessaires pour la requête, comme les sources de données, les attributs et les paramètres de qualité de la recherche.

Pour en savoir plus sur les applications de recherche, consultez l'article Créer une expérience de recherche personnalisée.

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

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

Configurer un projet

Lors de la configuration du projet :

  • sélectionnez le type de client Navigateur Web ;
  • indiquez l'URI d'origine de votre application ;
  • notez l'ID de client qui a été généré. Vous en aurez besoin pour effectuer les étapes suivantes. Le widget ne nécessite pas de code secret pour le client.

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

Ajouter un balisage HTML

Le widget nécessite très peu de code HTML pour fonctionner. Voici ce que vous devez indiquer :

  • Un élément input pour le champ de recherche
  • Un élément qui servira de point d'ancrage pour le pop-up de suggestion
  • Un élément qui contiendra les résultats de recherche
  • (Facultatif) Un élément qui contiendra les commandes d'attribut

L'extrait de code HTML suivant correspond à un widget Recherche; 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é de manière dynamique via un script chargeur. Pour ajouter le chargeur, utilisez la balise <script> comme suit :

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 ajouter un rappel onload dans la balise du script. La fonction est appelée une fois que le chargeur est prêt. Lorsque le chargeur est prêt, poursuivez le chargement du widget en appelant gapi.load() pour charger le client API, Google Sign-In et 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 fonction initializeApp() est appelée une fois tous les modules chargés.

Initialiser le widget

Commencez par initialiser la bibliothèque cliente en appelant gapi.client.init() ou gapi.auth2.init() avec l'ID de 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 :

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

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

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

Personnaliser l'expérience de connexion

Voici le principe de fonctionnement du widget : les utilisateurs sont invités à se connecter et à autoriser l'application dès qu'ils commencent à saisir une requête. Avec Google Sign-In for Websites, vous pouvez leur offrir une expérience de connexion plus personnalisée.

Autoriser les utilisateurs directement

La bibliothèque de Google Sign-In for Websites permet de contrôler l'état de connexion de l'utilisateur, et de le connecter ou le déconnecter lorsque cela est nécessaire. Dans l'exemple suivant, les changements d'état de connexion sont surveillés via isSignedIn et la méthode GoogleAuth.signIn() sert à déclencher la connexion à partir d'un clic sur 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 l'article Intégrer Google Sign-In dans votre application Web.

Connecter les utilisateurs automatiquement

Pour une expérience de connexion encore plus simple, il est possible de pré-autoriser l'application au nom des utilisateurs de votre organisation. Cette technique s'avère également utile lorsque vous protégez l'application avec Cloud Identity-Aware Proxy.

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

Personnaliser l'interface

Vous pouvez modifier l'apparence de l'interface de recherche en combinant diverses techniques :

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

Remplacer les styles avec du code CSS

Le widget Recherche est fourni avec un code CSS propre, qui sert à définir le style des éléments de suggestion et de résultat ainsi que les commandes de pagination, mais vous pouvez modifier l'apparence de ces éléments si besoin.

Le widget Recherche applique automatiquement sa feuille de style par défaut une fois que les feuilles de style de l'application ont été chargées, ce qui augmente le niveau de priorité des règles. Pour que vos styles prévalent sur ceux définis par défaut, renforcez la spécificité des règles par défaut à l'aide de sélecteurs d'ancêtre.

Par exemple, la règle suivante n'a aucun effet si elle est chargée dans une balise link ou style statique dans le 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é dans 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 l'article Classes CSS autorisées.

Décorer les éléments avec un adaptateur

Pour décorer un élément avant qu'il ne s'affiche, créez un adaptateur visant à ajouter une méthode de décoration (comme decorateSuggestionElement ou decorateSearchResultElement.), puis enregistrez-le.

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écorations peuvent modifier les attributs de l'élément conteneur ainsi que les éléments enfants. Elles sont susceptibles d'entraîner l'ajout ou la suppression d'éléments enfants. Par conséquent, lorsque vous modifiez la structure des éléments, créez directement les éléments voulus plutôt que d'utiliser des décorations.

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

Si vous souhaitez créer un élément personnalisé pour une suggestion, un conteneur d'attribut ou un résultat de recherche, créez et enregistrez un adaptateur qui ajoute createSuggestionElement, createFacetResultElement ou createSearchResultElement, respectivement.

Les adaptateurs suivants permettent de créer des éléments personnalisés de suggestion et de résultat de recherche à l'aide des balises 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;
}

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

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

Les restrictions suivantes s'appliquent lors de la création d'éléments d'attribut personnalisés avec la méthode createFacetResultElement :

  • 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 à l'aide de la classe CSS cloudsearch_facet_bucket_container.
  • Les buckets doivent s'afficher dans le même ordre que dans la réponse.

L'extrait de code suivant permet d'afficher les attributs sous forme de liens et non 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 de l'application de recherche représentent la configuration par défaut d'une interface de recherche et sont statiques. Pour ajouter des filtres ou des attributs dynamiques (afin que l'utilisateur puisse changer de source de données, par exemple), vous pouvez remplacer ces paramètres en interceptant la requête de recherche avec un adaptateur.

Ajoutez un adaptateur avec la méthode interceptSearchRequest pour modifier les requêtes adressées à l'API de recherche avant que celles-ci ne s'exécutent.

L'adaptateur suivant intercepte les requêtes à destination d'une source sélectionnée par l'utilisateur afin de les limiter :

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() lors de la création de 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 exécute à nouveau la requête si besoin.

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 en ajoutant interceptSearchResponse à l'adaptateur.

Verrouiller la version de l'API

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

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

Sécuriser l'interface de recherche

Les résultats de recherche contiennent des informations très sensibles. Respectez les bonnes pratiques pour protéger les applications Web, en particulier contre les attaques par détournement de clic.

Pour en savoir plus, consultez la page OWASP Guide Project (en anglais).