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

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

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

מקשטים את הרכיבים באמצעות מתאם

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

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

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

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

  • צריך לצרף את המחלקה cloudsearch_facet_bucket_clickable של ה-CSS לרכיב שמשתמשים לוחצים עליו כדי להחליף קטגוריה.
  • עליכם לכלול כל קטגוריה ברכיב מכיל באמצעות המחלקה של 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) {
  // ...
}

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

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

מטמיעים מתאם באמצעות method interceptSearchRequest כדי לשנות בקשות שנשלחו ל-Search API לפני הביצוע.

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

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

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

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

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

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

  return request;