Создайте поисковый интерфейс с виджетом поиска

Виджет поиска предоставляет настраиваемый интерфейс поиска для веб-приложений. Для реализации виджета требуется лишь небольшой объем HTML и JavaScript, и он поддерживает общие функции поиска, такие как фасеты и нумерация страниц. Вы также можете настроить части интерфейса с помощью CSS и JavaScript.

Если вам нужна большая гибкость, чем предлагает виджет, рассмотрите возможность использования API запросов. Информацию о создании интерфейса поиска с помощью API запросов см. в разделе Создание интерфейса поиска с помощью API запросов .

Создайте интерфейс поиска

Создание интерфейса поиска требует нескольких шагов:

  1. Настройка приложения поиска
  2. Создайте идентификатор клиента для приложения.
  3. Добавьте HTML-разметку для поля поиска и результатов.
  4. Загрузите виджет на страницу
  5. Инициализировать виджет

Настройка приложения поиска

Каждый интерфейс поиска должен иметь приложение поиска, определенное в консоли администратора. Приложение поиска предоставляет дополнительную информацию для запроса, такую ​​как источники данных, фасеты и настройки качества поиска.

Чтобы создать приложение поиска, обратитесь к разделу Создание пользовательского поиска .

Создайте идентификатор клиента для приложения.

Помимо действий, описанных в разделе «Настройка доступа к API Google Cloud Search» , вам также необходимо создать идентификатор клиента для веб-приложения.

Настройка проекта

При настройке проекта:

  • Выберите тип клиента веб-браузера
  • Укажите исходный URI вашего приложения.
  • Примечание о созданном идентификаторе клиента. Для выполнения следующих шагов вам понадобится идентификатор клиента. Секрет клиента для виджета не требуется.

Дополнительную информацию см. в разделе OAuth 2.0 для клиентского веб-приложения .

Добавить HTML-разметку

Для работы виджета требуется небольшой объем HTML. Вы должны предоставить:

  • Элемент input для поля поиска.
  • Элемент, к которому можно привязать всплывающее окно с предложением.
  • Элемент, содержащий результаты поиска.
  • (Необязательно) Укажите элемент, содержащий элементы управления фасетами.

В следующем фрагменте HTML показан HTML-код виджета поиска, в котором привязываемые элементы идентифицируются по атрибуту id :

обслуживание/виджет/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>

Загрузите виджет

Виджет загружается динамически с помощью скрипта-загрузчика. Чтобы включить загрузчик, используйте тег <script> как показано:

обслуживание/виджет/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>

Вы должны предоставить обратный вызов onload в теге сценария. Функция вызывается, когда загрузчик готов. Когда загрузчик будет готов, продолжайте загрузку виджета, вызвав gapi.load() чтобы загрузить клиент API, модули входа в Google и облачного поиска.

обслуживание/виджет/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)
}

Функция initializeApp() вызывается после загрузки всех модулей.

Инициализировать виджет

Сначала инициализируйте клиентскую библиотеку, вызвавgapi.client.init gapi.client.init() gapi.auth2.init() указав сгенерированный идентификатор клиента и область действия https://www.googleapis.com/auth/cloud_search.query . Затем используйте gapi.cloudsearch.widget.resultscontainer.Builder gapi.cloudsearch.widget.searchbox.Builder , чтобы настроить виджет и привязать его к элементам HTML.

В следующем примере показано, как инициализировать виджет:

обслуживание/виджет/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'
    });
  });
}

В приведенном выше примере используются две переменные для конфигурации, определенные как:

обслуживание/виджет/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/...";

Настройте процесс входа в систему

По умолчанию виджет предлагает пользователям войти в систему и авторизовать приложение в тот момент, когда они начинают вводить запрос. Вы можете использовать Google Sign-in для веб-сайтов , чтобы предложить пользователям более индивидуальный подход к входу.

Авторизуйте пользователей напрямую

Используйте «Войти через Google», чтобы отслеживать состояние входа пользователя и при необходимости входить или выходить из системы. Например, в следующем примере рассматривается состояние isSignedIn для отслеживания изменений входа в систему и используется метод GoogleAuth.signIn() для инициации входа в систему нажатием кнопки:

обслуживание/виджет/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();
};

Дополнительные сведения см. в разделе «Вход через Google» .

Автоматический вход пользователей

Вы можете еще больше упростить процесс входа в систему, предварительно авторизовав приложение от имени пользователей в вашей организации. Этот метод также полезен, если для защиты приложения используется прокси-сервер Cloud Identity Aware .

Дополнительную информацию см. в разделе Использование входа в Google с ИТ-приложениями .

Настройте интерфейс

Вы можете изменить внешний вид интерфейса поиска с помощью комбинации методов:

  • Переопределить стили с помощью CSS
  • Декорируем элементы переходником
  • Создание пользовательских элементов с помощью адаптера

Переопределить стили с помощью CSS

Виджет поиска поставляется с собственным CSS для стилизации элементов подсказок и результатов, а также элементов управления нумерацией страниц. При необходимости вы можете изменить стиль этих элементов.

Во время загрузки виджет поиска динамически загружает таблицу стилей по умолчанию. Это происходит после загрузки таблиц стилей приложения, что повышает приоритет правил. Чтобы гарантировать, что ваши собственные стили имеют приоритет над стилями по умолчанию, используйте селекторы предков, чтобы повысить специфичность правил по умолчанию.

Например, следующее правило не имеет эффекта, если оно загружено в статическую link или тег style в документе.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

Вместо этого укажите для правила идентификатор или класс родительского контейнера, объявленный на странице.

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

Список поддерживаемых классов и пример HTML, создаваемого виджетом, см. в справочнике по поддерживаемым классам CSS .

Декорируем элементы переходником

Чтобы украсить элемент перед отрисовкой, создайте и повторно зарегистрируйте адаптер, реализующий один из методов декорирования, например decorateSuggestionElement или decorateSearchResultElement.

Например, следующие адаптеры добавляют пользовательский класс к элементам предложения и результата.

обслуживание/виджет/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');
}

Чтобы зарегистрировать адаптер при инициализации виджета, используйте метод setAdapter() соответствующего класса Builder :

обслуживание/виджет/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();

Декораторы могут изменять атрибуты элемента-контейнера, а также любых дочерних элементов. Дочерние элементы могут быть добавлены или удалены во время оформления. Однако, если вы вносите структурные изменения в элементы, рассмотрите возможность непосредственного создания элементов, а не украшения.

Создание пользовательских элементов с помощью адаптера

Чтобы создать пользовательский элемент для предложения, контейнера фасетов или результатов поиска, создайте и зарегистрируйте адаптер, который реализует createSuggestionElement , createFacetResultElement или createSearchResultElement соответственно.

Следующие адаптеры иллюстрируют создание пользовательских элементов предложений и результатов поиска с использованием тегов HTML <template> .

обслуживание/виджет/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;
}

Чтобы зарегистрировать адаптер при инициализации виджета, используйте метод setAdapter() соответствующего класса Builder :

обслуживание/виджет/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();

Создание пользовательских фасетных элементов с помощью createFacetResultElement подвергается нескольким ограничениям:

  • Вы должны прикрепить класс CSS cloudsearch_facet_bucket_clickable к элементу, на который пользователи нажимают, чтобы переключить сегмент.
  • Вы должны обернуть каждый сегмент в содержащий его элемент с помощью CSS-класса cloudsearch_facet_bucket_container .
  • Вы не можете отображать сегменты в порядке, отличном от того, который указан в ответе.

Например, следующий фрагмент отображает фасеты с помощью ссылок вместо флажков.

обслуживание/виджет/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) {
  // ...
}

Настройте поведение поиска

Настройки приложения поиска представляют собой конфигурацию по умолчанию для интерфейса поиска и являются статическими. Чтобы реализовать динамические фильтры или фасеты, например разрешить пользователям переключать источники данных, вы можете переопределить настройки приложения поиска, перехватив поисковый запрос с помощью адаптера.

Реализуйте адаптер с методом interceptSearchRequest для изменения запросов, сделанных к API поиска, перед их выполнением.

Например, следующий адаптер перехватывает запросы, чтобы ограничить запросы источником, выбранным пользователем:

обслуживание/виджет/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;
}

Чтобы зарегистрировать адаптер при инициализации виджета, используйте метод setAdapter() при построенииResultContainer ResultsContainer

обслуживание/виджет/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();

Следующий HTML-код используется для отображения поля выбора для фильтрации по источникам:

обслуживание/виджет/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>

Следующий код прослушивает изменения, устанавливает выборку и при необходимости повторно выполняет запрос.

обслуживание/виджет/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);
  }
}

Вы также можете перехватить ответ поиска, реализовав interceptSearchResponse в адаптере.

Закрепите версию API

По умолчанию виджет использует последнюю стабильную версию API. Чтобы зафиксировать определенную версию, установите для параметра конфигурации cloudsearch.config/apiVersion предпочтительную версию перед инициализацией виджета.

обслуживание/виджет/public/basic/app.js
gapi.config.update('cloudsearch.config/apiVersion', 'v1');

Версия API по умолчанию будет равна 1.0, если она не установлена ​​или ей присвоено недопустимое значение.

Закрепите версию виджета

Чтобы избежать неожиданных изменений в интерфейсах поиска, установите параметр конфигурации cloudsearch.config/clientVersion , как показано:

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

Версия виджета по умолчанию будет равна 1.0, если она не установлена ​​или ей присвоено недопустимое значение.

Защитите интерфейс поиска

Результаты поиска содержат очень конфиденциальную информацию. Следуйте рекомендациям по защите веб-приложений, особенно от атак с использованием кликджекинга .

Для получения дополнительной информации см. Проект руководства OWASP.

Включить отладку

Используйте interceptSearchRequest , чтобы включить отладку виджета поиска. Например:

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

  return request;