יצירה של ממשק חיפוש באמצעות ווידג'ט החיפוש

ווידג'ט החיפוש מספק ממשק חיפוש שניתן להתאים אישית לאפליקציות אינטרנט. כדי להשתמש בווידג'ט צריך להשתמש רק ב-HTML וב-JavaScript. הפיצ'ר הזה מאפשר תכונות חיפוש נפוצות כמו מאפיינים ועימוד. תוכלו גם להתאים אישית חלקים בממשק באמצעות CSS ו-JavaScript.

אם אתם צריכים יותר גמישות מהווידג'ט, כדאי להשתמש ב-Query API. למידע על יצירת ממשק חיפוש באמצעות Query API, קראו את המאמר יצירת ממשק חיפוש באמצעות Query API.

פיתוח ממשק חיפוש

ליצירת ממשק החיפוש נדרשים מספר שלבים:

  1. הגדרה של אפליקציית חיפוש
  2. יצירת מזהה לקוח עבור האפליקציה
  3. הוספת תגי עיצוב של HTML בתיבת החיפוש ובתוצאות החיפוש
  4. טעינת הווידג'ט בדף
  5. מפעילים את הווידג'ט

הגדרה של אפליקציית חיפוש

בכל ממשק חיפוש חייבת להיות מוגדרת אפליקציית חיפוש במסוף Admin. אפליקציית החיפוש מספקת מידע נוסף על השאילתה, כמו מקורות הנתונים, המאפיינים והגדרות האיכות של החיפוש.

כדי ליצור אפליקציית חיפוש, קראו את המאמר יצירת חוויית חיפוש בהתאמה אישית.

יצירת מזהה לקוח עבור האפליקציה

בנוסף לשלבים של הגדרת גישה ל-Google Cloud Search API, צריך גם ליצור מזהה לקוח לאפליקציית האינטרנט.

הגדרת פרויקט

כשמגדירים את הפרויקט:

  • בוחרים את סוג הלקוח דפדפן אינטרנט
  • מציינים את URI המקור של האפליקציה.
  • הערה על מספר הלקוח שנוצר. כדי לבצע את השלבים הבאים, תצטרכו את מספר הלקוח. הסוד של הלקוח לא נדרש עבור הווידג'ט.

למידע נוסף, ראו OAuth 2.0 לאפליקציית אינטרנט בצד הלקוח.

הוספת תגי עיצוב של HTML

הווידג'ט דורש כמות קטנה של HTML כדי לפעול. עליכם לספק את הפרטים הבאים:

  • אלמנט input עבור תיבת החיפוש.
  • רכיב לעיגון החלון הקופץ של ההצעה.
  • רכיב שמכיל את תוצאות החיפוש.
  • (אופציונלי) מספקים רכיב שמכיל את פקדי ההיבטים.

קטע הקוד הבא ב-HTML מציג את ה-HTML של ווידג'ט חיפוש. במקום שבו הרכיבים שרוצים לקשר מזוהים, המאפיין: 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>

עליך לספק קריאה חוזרת (callback) של 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)
}

הפונקציה 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.

הדוגמה הבאה מראה איך להפעיל את הווידג'ט:

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 כדי לעקוב אחר מצב הכניסה של המשתמשים, וכדי להיכנס לחשבון או לצאת ממנו לפי הצורך. לדוגמה: בדוגמה של המצב isSignedIn אפשר לעקוב אחרי השינויים בכניסה לחשבון, והמערכת משתמשת בשיטה GoogleAuth.signIn() כדי להיכנס לחשבון באמצעות לחיצה על לחצן:

השירות/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();
};

לפרטים נוספים אפשר לעיין במאמר כניסה באמצעות חשבון Google.

כניסה אוטומטית של משתמשים

כדי לייעל עוד יותר את תהליך הכניסה, תוכלו לאשר מראש את הבקשה בשם המשתמשים בארגון. אפשר להשתמש בשיטה הזו גם אם משתמשים ב-Cloud Identity Aware Proxy כדי להגן על האפליקציה.

למידע נוסף, עיינו במאמר שימוש בכניסה באמצעות חשבון Google עם אפליקציות IT.

התאמה אישית של הממשק

תוכלו לשנות את המראה של ממשק החיפוש באמצעות שילוב של טכניקות:

  • שינוי הסגנונות באמצעות CSS
  • מקשטים את האלמנטים בעזרת מתאם
  • יצירת רכיבים מותאמים אישית באמצעות מתאם

שינוי הסגנונות באמצעות CSS

לווידג'ט החיפוש יש שירות CSS משלו לסגנון ורכיבי תוצאות, וגם לפקדי העימוד. אפשר לעצב מחדש את הרכיבים האלה לפי הצורך.

במהלך הטעינה, ווידג'ט החיפוש טוען באופן דינמי את גיליון הסגנונות שמוגדר כברירת מחדל. המצב הזה מתרחש אחרי שגיליונות הסגנונות של האפליקציות נטענים, כך שעדיפות הכללים גבוהה יותר. כדי להבטיח שהסגנונות שלכם יקבלו עדיפות על פני הסגנונות המוגדרים כברירת מחדל, תוכלו להשתמש בבוררי האב כדי להגביר את הספציפיות של כללי ברירת המחדל.

לדוגמה, הכלל הבא לא יושפע אם הוא נטען בתג link או בתג style במסמך.

.cloudsearch_suggestion_container {
  font-size: 14px;
}

במקום זאת, הגדירו את הכלל בהתאם למזהה או למחלקה של מאגר האבות המוצהר בדף.

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

רשימה של מחלקות תמיכה ו-HTML לדוגמה שהופקה על ידי הווידג'ט מופיעה בקובצי ה-CSS הנתמכים.

מקשטים את האלמנטים בעזרת מתאם

כדי לקשט רכיב לפני העיבוד, צריך ליצור ולרשום מתאם שמטמיע את אחת משיטות העיצוב, כמו decorateSuggestionElement או decorateSearchResultElement.

לדוגמה, המתאמים הבאים מוסיפים מחלקה מותאמת אישית להצעה ולרכיבי התוצאה.

serving/Widget/public/with_decorate_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 המתאימה:

serving/Widget/public/with_decorate_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 באופן רספונסיבי.

המתאמים הבאים ממחישים יצירת רכיבים מותאמים אישית עם הצעות ותוצאות חיפוש באמצעות תגי <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;
}

כדי להציב מחדש את המתאם כשמפעילים את הווידג'ט, צריך להשתמש בשיטה setAdapter() של המחלקה Builder המתאימה:

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

יצירת רכיבי היבט מותאמים אישית באמצעות createFacetResultElement כפופה למספר הגבלות:

  • צריך לצרף את מחלקת ה-CSS cloudsearch_facet_bucket_clickable לאלמנט שהמשתמשים לוחצים עליו כדי להחליף בין קטגוריות.
  • צריך להקיף את כל הקטגוריות ברכיב שמכיל את רמת ה-CSS cloudsearch_facet_bucket_container.
  • אי אפשר לעבד את הקטגוריות בסדר אחר מזה שהן מופיעות בתגובה.

לדוגמה, קטע הקוד הבא מעבד היבטים באמצעות קישורים במקום תיבות סימון.

הצגה/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 כדי לשנות את הבקשות שנשלחות ל-Search API לפני הביצוע.

לדוגמה, המתאם הבא מיירט בקשות להגבלת שאילתות למקור שנבחר על ידי משתמשים:

serving/Widget/public/with_request_○or/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

serving/Widget/public/with_request_○or/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_○or/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_○or/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 לגרסה המועדפת לפני הפעלת הווידג'ט.

serving/Widget/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;