Membuat antarmuka penelusuran dengan widget penelusuran

Widget penelusuran memberikan antarmuka penelusuran yang dapat disesuaikan untuk aplikasi web. Widget hanya membutuhkan sejumlah kecil HTML dan JavaScript untuk menerapkan dan mengaktifkan fitur penelusuran umum seperti faset dan penomoran halaman. Anda juga dapat menyesuaikan bagian antarmuka dengan CSS dan JavaScript.

Jika Anda membutuhkan lebih banyak fleksibilitas, selain yang ditawarkan oleh widget, pertimbangkan untuk menggunakan Query API. Untuk mengetahui informasi tentang cara membuat antarmuka penelusuran dengan Query API, lihat Membuat antarmuka penelusuran dengan Query API.

Membuat antarmuka penelusuran

Membuat antarmuka penelusuran memerlukan beberapa langkah:

  1. Mengonfigurasi aplikasi penelusuran
  2. Membuat ID klien untuk aplikasi
  3. Menambahkan markup HTML untuk kotak dan hasil penelusuran
  4. Memuat widget di halaman
  5. Menginisialisasi widget

Mengonfigurasi aplikasi penelusuran

Setiap antarmuka penelusuran harus memiliki aplikasi penelusuran yang ditentukan di konsol admin. Aplikasi penelusuran memberikan informasi tambahan untuk kueri, seperti sumber data, faset, dan pengaturan kualitas penelusuran.

Untuk membuat aplikasi penelusuran, lihat Membuat pengalaman penelusuran kustom.

Membuat ID klien untuk aplikasi

Selain langkah-langkah dalam Mengonfigurasi akses ke Google Cloud Search API, Anda juga harus membuat client ID untuk aplikasi web.

Mengonfigurasi project

Saat mengonfigurasi project:

  • Pilih jenis klien Browser web
  • Berikan URI asal aplikasi Anda.
  • Catat ID klien yang telah dibuat. Anda memerlukan ID klien untuk menyelesaikan langkah berikutnya. Rahasia klien tidak diperlukan untuk widget.

Untuk informasi tambahan, lihat OAuth 2.0 untuk Aplikasi Web Sisi Klien.

Menambahkan markup HTML

Widget membutuhkan sejumlah kecil HTML agar berfungsi. Anda harus menyediakan:

  • Elemen input untuk kotak penelusuran.
  • Elemen untuk mengaitkan pop-up saran.
  • Elemen untuk memuat hasil penelusuran.
  • (Opsional) Elemen untuk memuat kontrol faset.

Cuplikan HTML berikut ini menunjukkan HTML untuk widget penelusuran, yang mana elemen yang akan diikat diidentifikasi oleh atribut id-nya:

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>

Memuat widget

Widget dimuat secara dinamis melalui skrip pemuat. Untuk menyertakan loader, gunakan tag <script> seperti yang ditunjukkan:

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>

Anda harus memberikan callback onload dalam tag skrip. Fungsi ini dipanggil saat pemuat sudah siap. Saat pemuat siap, lanjutkan memuat widget dengan memanggil gapi.load() untuk memuat klien API, Login dengan Google, dan modul 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)
}

Fungsi initializeApp() dipanggil setelah semua modul dimuat.

Menginisialisasi widget

Pertama, lakukan inisialisasi library klien dengan memanggil gapi.client.init() atau gapi.auth2.init() dengan ID klien yang dihasilkan dan cakupan https://www.googleapis.com/auth/cloud_search.query. Selanjutnya, gunakan class gapi.cloudsearch.widget.resultscontainer.Builder dan gapi.cloudsearch.widget.searchbox.Builder untuk mengonfigurasi widget dan mengikatnya ke elemen HTML Anda.

Contoh berikut menunjukkan cara menginisialisasi widget:

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

Contoh di atas merujuk pada dua variabel untuk konfigurasi yang ditentukan sebagai:

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

Menyesuaikan pengalaman login

Secara default, widget akan meminta pengguna untuk login dan mengotorisasi aplikasi saat mereka mulai mengetik kueri. Anda dapat menggunakan Login dengan Google untuk Situs untuk menawarkan pengalaman login yang lebih disesuaikan bagi pengguna.

Mengotorisasi pengguna secara langsung

Gunakan Login dengan Google untuk memantau status login pengguna serta membuat pengguna login atau logout sesuai kebutuhan. Misalnya, contoh berikut mengamati status isSignedIn untuk memantau perubahan login dan menggunakan metode GoogleAuth.signIn() untuk memulai login dari klik tombol:

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

Untuk detail tambahan, lihat Login dengan Google.

Membuat pengguna login secara otomatis

Anda dapat menyederhanakan pengalaman login lebih jauh dengan mengotorisasi aplikasi terlebih dahulu atas nama pengguna di organisasi Anda. Teknik ini juga berguna jika menggunakan Cloud Identity Aware Proxy untuk menjaga keamanan aplikasi.

Untuk informasi tambahan, lihat Menggunakan Login dengan Google dengan Aplikasi IT.

Menyesuaikan antarmuka

Anda dapat mengubah tampilan antarmuka penelusuran melalui gabungan teknik:

  • Mengganti gaya dengan CSS
  • Mendekorasi elemen dengan adaptor
  • Membuat elemen kustom dengan adaptor

Mengganti gaya dengan CSS

Widget penelusuran dilengkapi dengan CSS-nya sendiri untuk memberi gaya pada saran dan elemen hasil serta kontrol penomoran halaman. Anda dapat mengatur ulang gaya pada elemen tersebut sesuai kebutuhan.

Selama pemuatan, widget penelusuran secara dinamis memuat lembar gaya defaultnya. Proses ini terjadi setelah lembar gaya aplikasi dimuat, sehingga meningkatkan prioritas aturan. Untuk memastikan gaya Anda lebih diprioritaskan daripada gaya default, gunakan pemilih ancestor untuk meningkatkan kekhususan aturan default.

Misalnya, aturan berikut tidak memiliki pengaruh jika dimuat di tag link atau style statis dalam dokumen.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

Sebagai gantinya, kualifikasi aturan dengan ID atau class container ancestor yang dinyatakan di halaman.

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

Untuk daftar class dukungan dan contoh HTML yang dihasilkan oleh widget, lihat referensi Class CSS yang didukung.

Mendekorasi elemen dengan adaptor

Untuk mendekorasi elemen sebelum rendering, buat dan daftarkan adaptor yang menerapkan salah satu metode dekorasi seperti decorateSuggestionElement atau decorateSearchResultElement.

Misalnya, adaptor berikut menambahkan class kustom ke elemen saran dan hasil.

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

Untuk mendaftarkan adaptor saat menginisialisasi widget, gunakan metode setAdapter() dari class Builder masing-masing:

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

Dekorator dapat mengubah atribut elemen container serta elemen turunan. Elemen turunan dapat ditambahkan atau dihapus selama proses dekorasi. Namun, jika membuat perubahan struktural pada elemen, pertimbangkan untuk membuat elemen secara langsung, bukan dengan menambahkan dekorasi.

Membuat elemen kustom dengan adaptor

Untuk membuat elemen kustom untuk saran, penampung faset, atau hasil penelusuran, buat dan daftarkan adaptor yang mengimplementasikan createSuggestionElement, createFacetResultElement, atau createSearchResultElement masing-masing.

Adaptor berikut menggambarkan pembuatan elemen hasil penelusuran dan saran kustom menggunakan tag <template> HTML.

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

Untuk mendaftarkan adaptor saat menginisialisasi widget, gunakan metode setAdapter() dari class Builder masing-masing:

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

Membuat elemen faset kustom dengan createFacetResultElement harus mematuhi beberapa pembatasan:

  • Anda harus menambahkan class CSS cloudsearch_facet_bucket_clickable ke elemen yang diklik pengguna untuk beralih bucket.
  • Anda harus menggabungkan setiap bucket dalam elemen yang dikandungnya dengan class CSS cloudsearch_facet_bucket_container.
  • Anda tidak dapat merender bucket dalam urutan yang berbeda dari yang muncul di respons.

Misalnya, cuplikan berikut merender faset menggunakan link, bukan kotak centang.

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

Menyesuaikan perilaku penelusuran

Setelan aplikasi penelusuran mewakili konfigurasi default untuk antarmuka penelusuran dan bersifat statis. Untuk menerapkan filter atau faset dinamis, seperti mengizinkan pengguna untuk beralih sumber data, Anda dapat mengganti setelan aplikasi penelusuran dengan menghalangi permintaan penelusuran menggunakan adaptor.

Terapkan adaptor dengan metode interceptSearchRequest untuk mengubah permintaan yang dibuat ke API penelusuran sebelum dieksekusi.

Misalnya, adaptor berikut menghalangi permintaan untuk membatasi kueri ke sumber yang dipilih pengguna:

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

Untuk mendaftarkan adaptor saat menginisialisasi widget, gunakan metode setAdapter() saat mem-build ResultsContainer

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 berikut digunakan untuk menampilkan kotak centang guna memfilter berdasarkan sumber:

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>

Kode berikut mendeteksi perubahan, menetapkan pilihan, dan mengeksekusi ulang kueri jika diperlukan.

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

Anda juga dapat menghalangi respons penelusuran dengan menerapkan interceptSearchResponse di adaptor.

Menyematkan versi API

Secara default, widget menggunakan API versi stabil terbaru. Untuk mengunci versi tertentu, tetapkan parameter konfigurasi cloudsearch.config/apiVersion ke versi yang disukai sebelum menginisialisasi widget.

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

Versi API akan ditetapkan ke 1.0 secara default jika tidak ditetapkan, atau ditetapkan ke nilai yang tidak valid.

Menyematkan versi widget

Untuk menghindari perubahan yang tidak diharapkan pada antarmuka penelusuran, tetapkan parameter konfigurasi cloudsearch.config/clientVersion seperti yang ditunjukkan:

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

Versi widget akan ditetapkan ke 1.0 secara default jika tidak ditetapkan, atau ditetapkan ke nilai yang tidak valid.

Mengamankan antarmuka penelusuran

Hasil penelusuran berisi informasi yang sangat sensitif. Ikuti praktik terbaik untuk mengamankan aplikasi web, khususnya terhadap serangan clickjacking.

Untuk mengetahui informasi selengkapnya, lihat Project Panduan OWASP

Mengaktifkan proses debug

Gunakan interceptSearchRequest untuk mengaktifkan proses debug untuk widget penelusuran. Contoh:

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

  return request;