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

Виджет поиска предоставляет настраиваемый интерфейс поиска для веб-приложений. Для реализации виджета требуется лишь небольшой фрагмент 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 :

обслуживать/виджет/публичный/с_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> , как показано ниже:

обслуживать/виджет/публичный/с_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 в теге script. Функция вызывается, когда загрузчик готов. После этого продолжите загрузку виджета, вызвав метод gapi.load() для загрузки API-клиента, входа через Google и модулей Cloud Search.

обслуживающий/виджет/публичный/с_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.auth2.init() с сгенерированным идентификатором клиента и областью действия https://www.googleapis.com/auth/cloud_search.query . Затем используйте классы gapi.cloudsearch.widget.resultscontainer.Builder и gapi.cloudsearch.widget.searchbox.Builder для настройки виджета и его привязки к HTML-элементам.

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

обслуживающий/виджет/публичный/с_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'
    });
  });
}

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

обслуживающий/виджет/публичный/с_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 для веб-сайтов», чтобы сделать процесс входа более удобным для пользователей.

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

Используйте функцию «Вход через Google» для отслеживания состояния входа пользователя и входа/выхода пользователей по мере необходимости. Например, в следующем примере отслеживается состояние isSignedIn для отслеживания изменений статуса входа и используется метод GoogleAuth.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 Proxy для защиты приложения.

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

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

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

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

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

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

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

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

.cloudsearch_suggestion_container {
  font-size: 14px;
}

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

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

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

Украсьте элементы с помощью адаптера.

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

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

обслуживающий/виджет/публичный/с_декорированным_элементом/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 :

обслуживающий/виджет/публичный/с_декорированным_элементом/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> .

обслуживающий/виджет/публичный/с_пользовательским_элементом/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 :

обслуживающий/виджет/публичный/с_пользовательским_элементом/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 .
  • Вы не можете отображать контейнеры в порядке, отличном от того, в котором они указаны в ответе.

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

обслуживающий/виджет/публичный/с_пользовательским_фасетом/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 поиска, перед их выполнением.

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

обслуживающий/виджет/публичный/с_перехватчиком_запросов/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() при построении ResultsContainer

обслуживающий/виджет/публичный/с_перехватчиком_запросов/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-код используется для отображения поля выбора для фильтрации по источникам:

обслуживающий/виджет/публичный/с_перехватчиком_запросов/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>

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

обслуживающий/виджет/публичный/с_перехватчиком_запросов/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 на предпочтительную версию перед инициализацией виджета.

обслуживающий/виджет/публичный/базовый/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;