검색 위젯으로 검색 인터페이스 만들기

검색 위젯은 웹 애플리케이션에 맞춤설정 가능한 검색 인터페이스를 제공합니다. HTML과 JavaScript를 최소한으로만 구현하면 되며 패싯 및 페이지로 나누기와 같은 일반적인 기능을 지원합니다. CSS와 JavaScript를 사용하여 인터페이스를 맞춤설정할 수도 있습니다.

유연성이 더 필요한 경우 Query API를 사용하세요. Query API로 검색 인터페이스 만들기를 참고하세요.

검색 인터페이스 빌드

검색 인터페이스를 빌드하려면 다음 단계를 따라야 합니다.

  1. 검색 애플리케이션을 구성합니다.
  2. 애플리케이션의 클라이언트 ID를 생성합니다.
  3. 검색창 및 결과용 HTML 마크업을 추가합니다.
  4. 페이지에 위젯을 로드합니다.
  5. 위젯을 초기화합니다.

검색 애플리케이션 구성

각 검색 인터페이스에는 관리 콘솔에서 정의된 검색 애플리케이션이 필요합니다. 애플리케이션은 데이터 소스, 패싯, 검색 품질 매개변수와 같은 쿼리 설정을 제공합니다.

검색 애플리케이션을 만들려면 맞춤 검색 환경 만들기를 참고하세요.

애플리케이션용 클라이언트 ID 생성

Cloud Search API에 대한 액세스 구성 단계 외에도 웹 애플리케이션용 클라이언트 ID를 생성합니다.

프로젝트 구성

프로젝트를 구성할 때 다음을 수행합니다.

  • 웹브라우저 클라이언트 유형을 선택합니다.
  • 앱의 원본 URI를 제공합니다.
  • 클라이언트 ID를 기록해 둡니다. 위젯에는 클라이언트 보안 비밀번호가 필요하지 않습니다.

자세한 내용은 클라이언트 측 웹 애플리케이션용 OAuth 2.0을 참고하세요.

HTML 마크업 추가

위젯에 필요한 HTML 요소는 다음과 같습니다.

  • 검색창의 input 요소
  • 제안 대화상자를 고정하는 요소
  • 검색 결과 요소
  • (선택사항) 패싯 컨트롤 요소입니다.

다음 스니펫은 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>

위젯 로드

<script> 태그를 사용하여 로더를 포함합니다.

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>

onload 콜백을 제공합니다. 로더가 준비되면 gapi.load()을 호출하여 API 클라이언트, Google 로그인, 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)
}

위젯 초기화

클라이언트 ID와 https://www.googleapis.com/auth/cloud_search.query 범위로 gapi.client.init() 또는 gapi.auth2.init()를 사용하여 클라이언트 라이브러리를 초기화합니다. 빌더 클래스를 사용하여 위젯을 구성하고 바인딩합니다.

초기화 예:

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

구성 변수:

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

로그인 환경 맞춤설정

사용자가 입력을 시작하면 위젯에서 로그인하라는 메시지를 표시합니다. 웹사이트용 Google 로그인을 사용하여 맞춤 환경을 제공할 수 있습니다.

사용자 직접 승인

Google 계정으로 로그인을 사용하여 로그인 상태를 모니터링하고 관리합니다. 이 예에서는 버튼 클릭 시 GoogleAuth.signIn()를 사용합니다.

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

사용자 자동 로그인

조직 내 사용자를 위해 애플리케이션을 사전 승인하여 로그인을 간소화합니다. Cloud Identity Aware Proxy와 함께 사용해도 유용합니다. IT 앱으로 Google 로그인 사용을 참고하세요.

인터페이스 맞춤설정

다음과 같은 방법으로 위젯의 모양을 변경할 수 있습니다.

  • CSS로 스타일 재정의
  • 어댑터를 사용하여 요소 데코레이션
  • 어댑터를 사용하여 커스텀 요소를 만듭니다.

CSS로 스타일 재정의

위젯에는 자체 CSS가 포함되어 있습니다. 이를 재정의하려면 상위 선택기를 사용하여 특정성을 높입니다.

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

지원되는 CSS 클래스 참조를 확인하세요.

어댑터를 사용하여 요소 데코레이션

렌더링 전에 요소를 수정하는 어댑터를 만들고 등록합니다. 이 예에서는 맞춤 CSS 클래스를 추가합니다.

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

초기화 중에 어댑터를 등록합니다.

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

어댑터를 사용하여 커스텀 요소 만들기

createSuggestionElement, createFacetResultElement 또는 createSearchResultElement를 구현하여 맞춤 UI 구성요소를 빌드합니다. 이 예에서는 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;
}

어댑터를 등록합니다.

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

맞춤 패싯 요소는 다음 규칙을 따라야 합니다.

  • 클릭 가능한 요소에 cloudsearch_facet_bucket_clickable를 연결합니다.
  • 각 버킷을 cloudsearch_facet_bucket_container로 래핑합니다.
  • 응답의 버킷 순서를 유지합니다.

예를 들어 다음 스니펫은 체크박스 대신 링크를 사용하여 속성을 렌더링합니다.

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) {
  // ...
}

검색 동작 맞춤설정

어댑터로 요청을 가로채서 검색 애플리케이션 설정을 재정의합니다. 실행 전에 요청을 수정하도록 interceptSearchRequest 구현 이 예에서는 선택한 소스로 쿼리를 제한합니다.

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

어댑터를 등록합니다.

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

다음 HTML은 소스별로 필터링할 선택 상자를 표시합니다.

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>

다음 코드는 변경사항을 리슨하고 선택사항을 설정한 후 필요한 경우 쿼리를 다시 실행합니다.

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

어댑터에서 interceptSearchResponse를 구현하여 검색 응답을 가로챌 수도 있습니다.

버전 고정

  • API 버전: 초기화 전에 cloudsearch.config/apiVersion를 설정합니다.
  • 위젯 버전: gapi.config.update('cloudsearch.config/clientVersion', 1.1)을 사용합니다.

둘 다 설정되지 않은 경우 기본값은 1.0입니다.

예를 들어 위젯을 버전 1.1에 고정하려면 다음을 실행합니다.

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

검색 인터페이스 보호

특히 클릭재킹을 방지하기 위해 웹 애플리케이션의 보안 권장사항을 따릅니다.

디버깅 사용 설정하기

interceptSearchRequest을 사용하여 디버깅을 사용 설정합니다.

if (!request.requestOptions) {
  request.requestOptions = {};
}
request.requestOptions.debugOptions = {enableDebugging: true};
return request;