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

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

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

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

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

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

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

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

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

Генерация идентификатора клиента для приложения

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

Настроить проект

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

  • Выберите тип клиента веб-браузера
  • Укажите исходный 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 Sign-in и Cloud Search.

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

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

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

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

обслуживание/виджет/общедоступный/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() для инициации входа одним нажатием кнопки:

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

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

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

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

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

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

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

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

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

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

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

serve/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>

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

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

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