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

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

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() ה-method המתאים של המחלקה 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;
}

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

יצירת רכיבי Facet מותאמים אישית באמצעות 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) {
  // ...
}

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

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

להטמיע מתאם עם interceptSearchRequest לשינוי בקשות שנשלחות אל 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;
}

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