Tworzenie interfejsu wyszukiwania za pomocą widżetu wyszukiwania

Widżet wyszukiwania umożliwia dostosowanie interfejsu wyszukiwania do aplikacji internetowych. Implementacja widżetu wymaga tylko niewielkiego fragmentu kodu HTML i JavaScript oraz włączenie typowych funkcji wyszukiwania, takich jak aspekty i podział na strony. Część interfejsu możesz też dostosowywać za pomocą arkuszy CSS i JavaScriptu.

Jeśli potrzebujesz więcej możliwości niż ten oferowany przez widżet, możesz użyć interfejsu Query API. Informacje o tworzeniu interfejsu wyszukiwania za pomocą interfejsu Query API znajdziesz w artykule Tworzenie interfejsu wyszukiwania przy użyciu interfejsu Query API.

Tworzenie interfejsu wyszukiwania

Tworzenie interfejsu wyszukiwania wymaga wykonania kilku czynności:

  1. Konfigurowanie wyszukiwarki
  2. Wygeneruj identyfikator klienta dla aplikacji
  3. Dodawanie znaczników HTML do pola wyszukiwania i wyników
  4. Wczytaj widżet na stronie
  5. Inicjowanie widżetu

Konfigurowanie wyszukiwarki

Każdy interfejs wyszukiwania musi mieć zdefiniowaną aplikację do wyszukiwania w konsoli administracyjnej. Wyszukiwarka udostępnia dodatkowe informacje dotyczące zapytania, takie jak źródła danych, aspekty i ustawienia jakości wyszukiwania.

Informacje o tworzeniu wyszukiwarki znajdziesz w artykule Tworzenie niestandardowej wyszukiwarki.

Wygeneruj identyfikator klienta dla aplikacji

Oprócz czynności opisanych w artykule Konfigurowanie dostępu do interfejsu API Google Cloud Search musisz też wygenerować identyfikator klienta aplikacji internetowej.

Konfigurowanie projektu

Podczas konfigurowania projektu:

  • Wybierz typ klienta Przeglądarka internetowa.
  • Podaj identyfikator URI źródła aplikacji.
  • Zapisz utworzony identyfikator klienta. Będzie on potrzebny do wykonania kolejnych kroków. W przypadku widżetu nie jest wymagany klucz klienta.

Więcej informacji znajdziesz w artykule Protokół OAuth 2.0 na potrzeby aplikacji internetowej po stronie klienta.

Dodaj znaczniki HTML

Do działania tego widżetu wymagana jest niewielka ilość kodu HTML. Musisz podać:

  • Element input dotyczący pola wyszukiwania.
  • Element, do którego można zakotwiczyć wyskakujące okienko z sugestią.
  • Element zawierający wyniki wyszukiwania.
  • (Opcjonalnie) Wypełnij element zawierający elementy sterujące aspektami.

Ten fragment kodu HTML zawiera kod HTML widżetu wyszukiwania, w którym elementy do powiązania są oznaczone atrybutem id:

wyświetlanie/widżet/publiczne/z_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>

Wczytaj widżet

Widżet jest ładowany dynamicznie przez skrypt wczytujący. Aby uwzględnić moduł wczytywania, użyj tagu <script> w ten sposób:

wyświetlanie/widżet/publiczne/z_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>

W tagu skryptu musisz podać wywołanie zwrotne onload. Funkcja jest wywoływana, gdy program wczytujący jest gotowy. Gdy moduł wczytywania będzie gotowy, przejdź dalej, aby wywołać widżet gapi.load() w celu załadowania modułów API, Logowania przez Google i Cloud Search.

wyświetlanie/widżet/publiczne/z_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)
}

Funkcja initializeApp() jest wywoływana po wczytaniu wszystkich modułów.

Inicjowanie widżetu

Najpierw zainicjuj bibliotekę klienta, wywołując metodę gapi.client.init() lub gapi.auth2.init() za pomocą wygenerowanego identyfikatora klienta i zakresu https://www.googleapis.com/auth/cloud_search.query. Następnie użyj klasy gapi.cloudsearch.widget.resultscontainer.Builder i gapi.cloudsearch.widget.searchbox.Builder, aby skonfigurować widżet i powiązać go z elementami HTML.

Poniższy przykład pokazuje, jak zainicjować widżet:

wyświetlanie/widżet/publiczne/z_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'
    });
  });
}

Powyższy przykład odwołuje się do dwóch zmiennych konfiguracji skonfigurowanych jako:

wyświetlanie/widżet/publiczne/z_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/...";

Dostosowywanie procesu logowania

Domyślnie widżet wyświetla prośbę o zalogowanie się i autoryzację aplikacji w momencie rozpoczęcia wpisywania zapytania. Aby ułatwić użytkownikom logowanie się, możesz skorzystać z funkcji Logowanie przez Google na stronach internetowych.

Autoryzuj użytkowników bezpośrednio

Użyj funkcji Zaloguj się przez Google, aby monitorować stan logowania użytkownika i zależnie od potrzeb wylogowywania się. Poniższy przykład pokazuje stan isSignedIn, aby monitorować zmiany logowania, i stosuje metodę GoogleAuth.signIn(), aby zainicjować logowanie z kliknięcia przycisku:

wyświetlanie/widżet/publiczne/z_logowaniem/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();
};

Więcej informacji znajdziesz w artykule Logowanie przez Google.

Loguj użytkowników automatycznie

Możesz jeszcze bardziej uprościć logowanie, autoryzując aplikację w imieniu użytkowników w organizacji. Ta metoda jest przydatna też wtedy, gdy do zabezpieczania aplikacji używasz Cloud Identity Aware Proxy.

Więcej informacji znajdziesz w artykule Korzystanie z logowania przez Google w aplikacjach IT.

Dostosowywanie interfejsu

Wygląd interfejsu wyszukiwania można zmienić za pomocą kombinacji technik:

  • Zastąp style CSS
  • Dekorowanie elementów za pomocą przejściówki
  • Tworzenie elementów niestandardowych za pomocą przejściówki

Zastąp style CSS

Widżet wyszukiwania jest wyposażony we własną usługę porównywania cen, która służy do określania stylu sugestii i wyników wyników. W razie potrzeby możesz zmienić ich styl.

Podczas ładowania widżet wyszukiwania dynamicznie wczytuje domyślny arkusz stylów. Dzieje się tak po wczytaniu arkuszy stylów aplikacji, co podnosi priorytet reguł. Aby mieć pewność, że Twoje style mają pierwszeństwo przed stylami domyślnymi, użyj selektorów nadrzędnych, aby zwiększyć szczegółowość reguł domyślnych.

Na przykład poniższa reguła nie działa, jeśli zostanie załadowany w statycznym tagu link lub style w dokumencie.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

Zamiast tego zakwalifikuj regułę za pomocą identyfikatora lub klasy kontenera nadrzędnego zadeklarowanego na stronie.

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

Listę klas pomocy i przykładowy kod HTML wygenerowany przez widżet znajdziesz w artykule Obsługiwane klasy CSS.

Dekorowanie elementów za pomocą przejściówki

Aby ozdobić element przed wyrenderowaniem, utwórz i ponownie przejmij adapter, który implementuje jedną z metod dekorowania, np. decorateSuggestionElement lub decorateSearchResultElement..

Na przykład poniższy adapter dodaje klasę niestandardową do elementów sugestii i wyników.

wyświetlanie/widżet/publiczne/z_dekorowanym_elementem/aplikacja.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');
}

Aby zarejestrować adapter podczas inicjowania widżetu, użyj metody setAdapter() odpowiedniej klasy Builder:

wyświetlanie/widżet/publiczne/z_dekorowanym_elementem/aplikacja.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();

Dekoratory mogą modyfikować atrybuty elementu kontenera oraz wszystkie elementy podrzędne. Elementy podrzędne można dodawać i usuwać podczas dekorowania. Jeśli jednak wprowadzasz zmiany strukturalne w elementach, rozważ utworzenie elementów bezpośrednio, zamiast je dekorować.

Tworzenie elementów niestandardowych za pomocą przejściówki

Aby utworzyć element niestandardowy dla sugestii, kontenera aspektu lub wyniku wyszukiwania, utwórz i zarejestruj adapter, który wdraża createSuggestionElement, createFacetResultElement lub createSearchResultElement.

Poniższy artykuł przedstawia tworzenie niestandardowych sugestii i elementów wyników wyszukiwania za pomocą tagów HTML <template>.

wyświetlanie/widżet/publiczne/z_niestandardowym_elementem/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;
}

Aby ponownie umieścić adapter podczas inicjowania widżetu, użyj metody setAdapter() odpowiedniej klasy Builder:

wyświetlanie/widżet/publiczne/z_niestandardowym_elementem/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();

Tworzenie niestandardowych elementów aspektu za pomocą createFacetResultElement podlega kilku ograniczeniom:

  • Musisz dołączyć klasę CSS cloudsearch_facet_bucket_clickable do elementu, który użytkownicy klikają, aby przełączyć zasobnik.
  • Każdy zasobnik należy umieścić w elemencie zawierającym klasę CSS cloudsearch_facet_bucket_container.
  • Zasobników nie można renderować w innej kolejności niż w odpowiedzi.

Na przykład poniższy fragment kodu renderuje aspekty za pomocą linków, a nie pól wyboru.

wyświetlanie/widżet/publiczne/z_niestandardową_twarzą/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) {
  // ...
}

Dostosowywanie działania wyszukiwania

Ustawienia wyszukiwarki odzwierciedlają domyślną konfigurację interfejsu wyszukiwania. Aby zaimplementować filtry lub aspekty dynamiczne, takie jak umożliwianie użytkownikom przełączania źródeł danych, możesz zastąpić ustawienia wyszukiwarki, przechwytując żądanie wyszukiwania za pomocą adaptera.

Zaimplementuj adapter za pomocą metody interceptSearchRequest, aby zmodyfikować żądania przesłane do interfejsu Search API przed wykonaniem.

Na przykład ten adapter przechwytuje żądania ograniczające zapytania do źródła wybranego przez użytkownika:

wyświetlanie/widżet/publiczne/z_przechwytywaniem_żądań/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;
}

Aby zarejestrować adapter podczas inicjowania widżetu, użyj metody setAdapter() podczas tworzenia ResultsContainer

wyświetlanie/widżet/publiczne/z_przechwytywaniem_żądań/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();

Aby wyświetlić pole wyboru do filtrowania według źródeł, używany jest ten kod HTML:

wyświetlanie/widżet/publiczne/z_przechwytywaniem_żądań/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>

Ten kod nasłuchuje zmiany, ustawia wybór i w razie potrzeby ponownie uruchamia zapytanie.

wyświetlanie/widżet/publiczne/z_przechwytywaniem_żądań/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);
  }
}

Możesz też przechwycić odpowiedź wyszukiwania, stosując w adapcie element interceptSearchResponse.

Przypnij wersję interfejsu API

Domyślnie widżet używa najnowszej stabilnej wersji interfejsu API. Aby zablokować konkretną wersję, przed zainicjowaniem widżetu ustaw parametr konfiguracji cloudsearch.config/apiVersion na preferowaną wersję.

wyświetlanie/widżet/publiczne/podstawowe/app.js
gapi.config.update('cloudsearch.config/apiVersion', 'v1');

Jeśli nie jest skonfigurowana lub ma nieprawidłową wartość, wersja interfejsu API jest domyślnie ustawiana na wartość 1.0.

Przypinanie wersji widżetu

Aby uniknąć nieoczekiwanych zmian w interfejsach wyszukiwania, ustaw parametr konfiguracji cloudsearch.config/clientVersion w ten sposób:

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

Jeśli nie skonfigurowano tej opcji lub jej wartość jest nieprawidłowa, wersja widżetu jest domyślnie ustawiana na wartość 1,0.

Zabezpieczanie interfejsu wyszukiwania

Wyniki wyszukiwania zawierają informacje poufne. Stosuj sprawdzone metody zabezpieczania aplikacji internetowych, zwłaszcza na potrzeby ataków polegających na przejęciu konta.

Więcej informacji znajdziesz w projekcie przewodnika OWASP.

Włącz debugowanie

Użyj narzędzia interceptSearchRequest, aby włączyć debugowanie widżetu wyszukiwania. Na przykład:

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

  return request;