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

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

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

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

כדי ליצור את ממשק החיפוש, צריך לבצע את השלבים הבאים:

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

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

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

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

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

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

הגדרת פרויקט

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

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

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

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

הווידג'ט דורש את רכיבי ה-HTML הבאים:

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

בקטע הקוד הזה מוצגים רכיבים שזוהו לפי מאפייני 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>

טעינת הווידג'ט

מוסיפים את ה-loader באמצעות תג <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>

מבקשים 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)
}

הפעלת הווידג'ט

מפעילים את ספריית הלקוח באמצעות gapi.client.init() או gapi.auth2.init() עם מזהה הלקוח וההיקף https://www.googleapis.com/auth/cloud_search.query. משתמשים במחלקות של כלי הפיתוח כדי להגדיר את הווידג'ט ולקשר אותו.

דוגמה לאתחול:

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. בדוגמה הזו נעשה שימוש ב-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();
};

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

נותנים מראש הרשאה לאפליקציה למשתמשים בארגון כדי לייעל את תהליך הכניסה. האפשרות הזו שימושית גם עם שרת proxy לאימות זהויות (IAP) ב-Cloud Identity. אפשר לעיין במאמר בנושא כניסה באמצעות חשבון Google באפליקציות IT.

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

כדי לשנות את המראה של הווידג'ט:

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

החלפת סגנונות באמצעות CSS

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

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

אפשר לעיין בהפניה למחלקות CSS נתמכות.

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

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

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

רישום המתאם במהלך האתחול:

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

רושמים את המתאם:

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

רכיבי פנים בהתאמה אישית צריכים לעמוד בכללים הבאים:

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

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

רושמים את המתאם:

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 version: מגדירים את cloudsearch.config/apiVersion לפני ההפעלה.
  • גרסת הווידג'ט: משתמשים ב-gapi.config.update('cloudsearch.config/clientVersion', 1.1).

אם לא מגדירים ערך לפרמטרים האלה, ערך ברירת המחדל שלהם הוא 1.0.

לדוגמה, כדי להצמיד את הווידג'ט לגרסה 1.1:

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

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

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

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

כדי להפעיל ניפוי באגים, משתמשים ב-interceptSearchRequest:

if (!request.requestOptions) {
  request.requestOptions = {};
}
request.requestOptions.debugOptions = {enableDebugging: true};
return request;