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

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

מתן הרשאה ישירה למשתמשים

בעזרת Sign In with Google תוכלו לעקוב אחרי מצב הכניסה של המשתמש, לבצע כניסה או יציאה מהחשבון לפי הצורך. לדוגמה, הדוגמה הבאה רואה את המצב isSignedIn כדי לעקוב אחר שינויים בכניסה לחשבון, ומשתמשת בשיטה 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();
};

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

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

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

למידע נוסף, אפשר לעיין במאמר בנושא שימוש ב'כניסה באמצעות חשבון 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_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 המתאימה:

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 באופן אוטומטי.

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

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

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

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

התאמה אישית של התנהגות החיפוש

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

כדי לשנות בקשות שנשלחות ל-Search API לפני ההפעלה, מטמיעים מתאם באמצעות השיטה 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;
}

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

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

כברירת מחדל, הווידג'ט משתמש בגרסה היציבה והעדכנית ביותר של ה-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 אם היא לא מוגדרת או מוגדרת כערך לא חוקי.

אבטחת ממשק החיפוש

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

למידע נוסף, ראו פרויקט OWASP Guide Project

הפעלת ניפוי באגים

אפשר להשתמש ב-interceptSearchRequest כדי להפעיל את ניפוי הבאגים בווידג'ט החיפוש. למשל:

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

  return request;