הוספת רכיבים אינטראקטיביים של ממשק משתמש לכרטיסים

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

אפליקציות ל-Chat יכולות להשתמש בממשקי Chat הבאים כדי ליצור כרטיסים אינטראקטיביים:

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

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


אתם יכולים להשתמש בכלי ליצירת כרטיסים כדי לעצב ולראות תצוגה מקדימה של הודעות וממשקי משתמש לאפליקציות ל-Chat:

פתיחת הכלי ליצירת כרטיסים

דרישות מוקדמות

אפליקציה ל-Google Chat שמוגדרת לקבל אינטראקציות של משתמשים ולהגיב להן. כדי ליצור אפליקציית Chat אינטראקטיבית, צריך להשלים אחד מהמדריכים לתחילת העבודה שמפורטים בהמשך, בהתאם לארכיטקטורת האפליקציה שבה רוצים להשתמש:

הוספת כפתור

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

  • פתיחת היפר-קישור באמצעות OpenLink, כדי לספק למשתמשים מידע נוסף.
  • להפעיל action שמריץ פונקציה בהתאמה אישית, כמו קריאה ל-API.

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

הוספת לחצן שמפעיל פונקציה מותאמת אישית

זו דוגמה לכרטיס שמורכב מווידג'ט ButtonList עם שני לחצנים. לחצן אחד פותח את מאמרי העזרה למפתחים של Google Chat בכרטיסייה חדשה. הכפתור השני מפעיל פונקציה מותאמת אישית בשם goToView() ומעביר את הפרמטר viewType="BIRD EYE VIEW".

הוספת לחצן בסגנון Material Design

בדוגמה הבאה מוצגים כמה כפתורים בסגנונות שונים של כפתורים ב-Material Design.

כדי להחיל סגנון Material Design, אל תכללו את מאפיין הצבע.

הוספת כפתור עם צבע בהתאמה אישית וכפתור מושבת

אפשר למנוע ממשתמשים ללחוץ על לחצן באמצעות ההגדרה "disabled": "true".

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

הוספת כפתור עם סמל

בתמונה הבאה מוצג כרטיס שמורכב מווידג'ט ButtonList עם שני ווידג'טים של סמלים Button. באחד הלחצנים נעשה שימוש בשדה knownIcon כדי להציג את סמל האימייל המובנה של Google Chat. הכפתור השני משתמש בשדה iconUrl כדי להציג ווידג'ט של סמל בהתאמה אישית.

הוספת כפתור עם סמל וטקסט

בכרטיס הבא מוצג ווידג'ט ButtonList שמבקש מהמשתמש לשלוח אימייל. בכפתור הראשון מוצג סמל של אימייל ובכפתור השני מוצג טקסט. המשתמש יכול ללחוץ על הסמל או על לחצן הטקסט כדי להפעיל את הפונקציה sendEmail.

התאמה אישית של הכפתור לקטע שניתן לכיווץ

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

הוספת תפריט אפשרויות נוספות

אפשר להשתמש ב-Overflow menu בכרטיסי Chat כדי להציע אפשרויות ופעולות נוספות. כך אפשר לכלול יותר אפשרויות בלי שהממשק של הכרטיס יהיה עמוס מדי, ולוודא שהעיצוב נקי ומאורגן.

הוספה של רשימת צ'יפים

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

איסוף מידע מהמשתמשים

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

איך אוספים ומעבדים מידע ממשתמשי Google Chat

איסוף טקסט

בווידג'ט TextInput יש שדה שבו המשתמשים יכולים להזין טקסט. בווידג'ט יש תמיכה בהצעות, שעוזרות למשתמשים להזין נתונים אחידים, ובפעולות on-change, שהן Actions שמופעלות כשמתרחש שינוי בשדה להזנת טקסט, כמו כשמשתמש מוסיף או מוחק טקסט.

כשאתם צריכים לאסוף נתונים לא מוחשיים או לא ידועים מהמשתמשים, אתם יכולים להשתמש בווידג'ט TextInput. כדי לאסוף נתונים מוגדרים מהמשתמשים, צריך להשתמש בווידג'ט SelectionInput.

זוהי דוגמה לכרטיס שמורכב מווידג'ט TextInput:

איסוף תאריכים או שעות

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

בתמונה הבאה מוצג כרטיס שמורכב משלושה סוגים שונים של DateTimePickerווידג'טים:

המשתמשים יכולים לבחור פריטים

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

בווידג'ט SelectionInput יש תמיכה בהצעות, שעוזרות למשתמשים להזין נתונים אחידים, ובפעולות on-change, שהן Actions שמופעלות כשמתרחש שינוי בשדה להזנת קלט של בחירה, למשל כשמשתמש בוחר פריט או מבטל את הבחירה שלו.

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

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

הוספה של תיבת סימון

בדוגמה הבאה מוצג כרטיס שבו המשתמש מתבקש לציין אם איש הקשר הוא מקצועי, אישי או שניהם, עם ווידג'ט SelectionInput שמשתמש בתיבות סימון:

הוספת כפתור בחירה

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

הוספת מתג

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

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

איך מאכלסים באופן דינמי תפריטים נפתחים

זמין באפליקציות ל-Google Chat.

אתם יכולים לאכלס באופן דינמי פריטים לתפריט נפתח ממקורות נתונים ב-Google Workspace או ממקור נתונים חיצוני. כדי להשתמש במקורות נתונים דינמיים, מציינים את השדה data_source_configs, שהוא מערך של אובייקטים מסוג DataSourceConfig. כל DataSourceConfig יכול להכיל platformDataSource או remoteDataSource. בשלב הזה יש תמיכה רק ב-DataSourceConfig אחד.

איכלוס פריטים מ-Google Workspace

כדי לאכלס פריטים ממקורות נתונים של Google Workspace, כמו משתמשי Google Workspace, צריך לציין את השדה platformDataSource בתוך DataSourceConfig. בניגוד לשימוש ב-items סטטי, לא צריך להשתמש באובייקטים של SelectionItem, כי פריטי הבחירה האלה מגיעים באופן דינמי מ-Google Workspace.

בדוגמה הבאה מוצג תפריט נפתח שמאוכלס במשתמשי Google Workspace:

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "contacts",
            "type": "DROPDOWN",
            "label": "Select contact from organization",
            "data_source_configs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "min_characters_trigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
איכלוס פריטים ממקור נתונים חיצוני

כדי לאכלס פריטים ממקור נתונים חיצוני או של צד שלישי, כמו מערכת לניהול קשרי לקוחות (CRM), משתמשים בשדה remoteDataSource בתוך DataSourceConfig כדי לציין פונקציה שמחזירה פריטים ממקור הנתונים.

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

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "widgets": [
        {
          "selectionInput": {
            "name": "crm_leads",
            "type": "DROPDOWN",
            "label": "Select CRM Lead",
            "data_source_configs": [
              {
                "remoteDataSource": {
                  "function": "getCrmLeads"
                },
                "min_characters_trigger": 2
              }
            ],
            "items": [
              {
                "text": "Suggested Lead 1",
                "value": "lead-1"
              }
            ]
          }
        }
      ]
    }
  ]
}

כדי לצמצם את מספר הבקשות למקור נתונים דינמי, אפשר לכלול פריטים מוצעים שמופיעים בתפריט הנפתח לפני שהמשתמשים מקלידים. אפשר גם להגדיר את התפריט הנפתח להשלמה אוטומטית של פריטים על סמך מה שהמשתמשים מקלידים, על ידי הגדרת min_characters_trigger בתוך DataSourceConfig. כשמשתמש מקליד לפחות את מספר התווים שצוין ב-min_characters_trigger, מופעלת הפונקציה שצוינה ב-remoteDataSource. אובייקט האירוע שמועבר לפונקציה כולל את הקלט של המשתמש במפתח autocomplete_widget_query.

הוספת תפריט בחירה מרובה

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

אפשר לאכלס פריטים בתפריט בחירה מרובה ממקורות הנתונים הבאים ב-Google Workspace:

  • משתמשי Google Workspace: אפשר להוסיף רק משתמשים מאותו ארגון Google Workspace.
  • מרחבים ב-Chat: המשתמש שמזין פריטים בתפריט הבחירה המרובה יכול לראות ולבחור רק מרחבים שהוא חבר בהם בארגון שלו ב-Google Workspace.

כדי להשתמש במקורות נתונים של Google Workspace, צריך לציין את השדה platformDataSource. בשונה מסוגים אחרים של קלט לבחירה, לא צריך להוסיף אובייקטים של SelectionItem כי פריטי הבחירה האלה נלקחים באופן דינמי מ-Google Workspace.

בדוגמה הבאה אפשר לראות תפריט בחירה מרובה של משתמשי Google Workspace. כדי לאכלס משתמשים, קלט הבחירה מגדיר את commonDataSource ל-USER:

JSON

{
  "selectionInput": {
    "name": "contacts",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 5,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "commonDataSource": "USER"
    }
  }
}

בדוגמה הבאה מוצג תפריט בחירה מרובה של מרחבים ב-Chat. כדי לאכלס את המרחבים, קלט הבחירה מציין את השדה hostAppDataSource. בתפריט לבחירה מרובה מוגדר גם defaultToCurrentSpace ל-true, כך שהמרחב הנוכחי הוא ברירת המחדל בתפריט:

JSON

{
  "selectionInput": {
    "name": "spaces",
    "type": "MULTI_SELECT",
    "label": "Selected contacts",
    "multiSelectMaxSelectedItems": 3,
    "multiSelectMinQueryLength": 1,
    "platformDataSource": {
      "hostAppDataSource": {
        "chatDataSource": {
          "spaceDataSource": {
            "defaultToCurrentSpace": true
          }
        }
      }
    }
  }
}

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

כדי להשתמש במקור נתונים חיצוני, צריך להשתמש בשדה externalDataSource כדי לציין פונקציה שמחזירה פריטים ממקור הנתונים.

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

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

Node.js

node/chat/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: FUNCTION_URL },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getSuggestedContact("3")]
}

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Python

python/chat/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': FUNCTION_URL },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_suggested_contact("3")]
}

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  .setItems(List.of(getSuggestedContact("3")))))))))));

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Apps Script

בדוגמה הזו, הודעת הכרטיס נשלחת על ידי החזרת JSON של הכרטיס. אפשר גם להשתמש בשירות הכרטיסים של Apps Script.

apps-script/chat/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "queryContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getSuggestedContact("3")]
}

איך מאכלסים פריטים מוצעים ממקור נתונים דינמי

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

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

  1. טיפול באובייקט אירוע שאפליקציית Chat מקבלת כשמשתמשים מקלידים בתפריט.
  2. מתוך אובייקט האירוע, מקבלים את הערך שהמשתמש הקליד, שמיוצג בשדה event.commonEventObject.parameters["autocomplete_widget_query"].
  3. שליחת שאילתה למקור הנתונים באמצעות ערך קלט של משתמשים כדי לקבל SelectionItems אחת או יותר להצעה למשתמש.
  4. כדי להחזיר פריטים מוצעים, מחזירים את הפעולה RenderActions עם אובייקט modifyCard.

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

Node.js

node/chat/selection-input/index.js
/**
 * Web app that responds to events sent from a Google Chat space.
 *
 * @param {Object} req Request sent from Google Chat space
 * @param {Object} res Response to send back
 */
app.post('/', async (req, res) => {
  // Stores the Google Chat event
  const chatEvent = req.body.chat;

  // Handle user interaction with multiselect.
  if(chatEvent.widgetUpdatedPayload) {
    return res.json(queryContacts(req.body));
  }

  // Replies with a card that contains the multiselect menu.
  return res.json({ hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: FUNCTION_URL },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}});
});

/**
 * Get contact suggestions based on text typed by users.
 *
 * @param {Object} event the event object that contains the user's query
 * @return {Object} suggestions
 */
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
  // Only return items based on the query from the user.
  ].filter(e => !query || e.text.includes(query)) }}}]}};
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a selection item in the menu.
 */
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Python

python/chat/selection-input/main.py
@app.route('/', methods=['POST'])
def post() -> Mapping[str, Any]:
  """Handle requests from Google Chat

  Returns:
      Mapping[str, Any]: The response
  """
  # Stores the Google Chat event
  chatEvent = request.get_json().get('chat')

  # Handle user interaction with multiselect.
  if chatEvent.get('widgetUpdatedPayload') is not None:
    return json.jsonify(query_contacts(request.get_json()))

  # Replies with a card that contains the multiselect menu.
  return json.jsonify({ 'hostAppDataAction': { 'chatDataAction': { 'createMessageAction': {
    'message': { 'cardsV2': [{
      'cardId': "contactSelector",
      'card': { 'sections':[{ 'widgets': [{
        'selectionInput': {
          'name': "contacts",
          'type': "MULTI_SELECT",
          'label': "Selected contacts",
          'multiSelectMaxSelectedItems': 3,
          'multiSelectMinQueryLength': 1,
          'externalDataSource': { 'function': FUNCTION_URL },
          # Suggested items loaded by default.
          # The list is static here but it could be dynamic.
          'items': [get_suggested_contact("3")]
        }
      }]}]}
    }]}
  }}}})


def query_contacts(event: dict) -> dict:
  """Get contact suggestions based on text typed by users.

  Args:
      event (Mapping[str, Any]): The event object that contains the user's query

  Returns:
      Mapping[str, Any]: The response with contact suggestions.
  """
  query = event.get("commonEventObject").get("parameters").get("autocomplete_widget_query")
  return { 'action': { 'modifyOperations': [{ 'updateWidget': { 'selectionInputWidgetSuggestions': { 'suggestions': list(
    filter(lambda e: query is None or query in e["text"], [
      # The list is static here but it could be dynamic.
      get_suggested_contact("1"), get_suggested_contact("2"), get_suggested_contact("3"), get_suggested_contact("4"), get_suggested_contact("5")
    # Only return items based on the query from the user
    ])
  )}}}]}}


def get_suggested_contact(id: str) -> dict:
  """Generate a suggested contact given an ID.

  Args:
      id (str): The ID of the contact to return.

  Returns:
      Mapping[str, Any]: The contact formatted as a selection item in the menu.
  """
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Java

java/chat/selection-input/src/main/java/com/google/chat/selectionInput/App.java
@SpringBootApplication
@RestController
// Web app that responds to events sent from a Google Chat space.
public class App {
  private static final String FUNCTION_URL = "your-function-url";

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

  /**
   * Handle requests from Google Chat
   * 
   * @param event the event object sent by Google Chat
   * @return The response to be sent back to Google Chat
   */
  @PostMapping("/")
  @ResponseBody
  public GenericJson onEvent(@RequestBody JsonNode event) throws Exception {
    // Stores the Google Chat event
    JsonNode chatEvent = event.at("/chat");

    // Handle user interaction with multiselect.
    if (!chatEvent.at("/widgetUpdatedPayload").isEmpty()) {
      return queryContacts(event);
    }

    // Replies with a card that contains the multiselect menu.
    Message message = new Message().setCardsV2(List.of(new CardWithId()
      .setCardId("contactSelector")
      .setCard(new GoogleAppsCardV1Card()
        .setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(new GoogleAppsCardV1Widget()
          .setSelectionInput(new GoogleAppsCardV1SelectionInput()
            .setName("contacts")
            .setType("MULTI_SELECT")
            .setLabel("Selected contacts")
            .setMultiSelectMaxSelectedItems(3)
            .setMultiSelectMinQueryLength(1)
            .setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
            // Suggested items loaded by default.
            // The list is static here but it could be dynamic.
            .setItems(List.of(getSuggestedContact("3")))))))))));

    return new GenericJson() {{
      put("hostAppDataAction", new GenericJson() {{
        put("chatDataAction", new GenericJson() {{
          put("createMessageAction", new GenericJson() {{
            put("message", message);
          }});
        }});
      }});
    }};
  }

  /**
   * Get contact suggestions based on text typed by users.
   *
   * @param event the event object that contains the user's query.
   * @return The response with contact suggestions.
   */
  GenericJson queryContacts(JsonNode event) throws Exception {
    String query = event.at("/commonEventObject/parameters/autocomplete_widget_query").asText();
    List<GoogleAppsCardV1SelectionItem> suggestions = List.of(
      // The list is static here but it could be dynamic.
      getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
    // Only return items based on the query from the user
    ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList();

    return new GenericJson() {{
      put("action", new GenericJson() {{
        put("modifyOperations", List.of(new GenericJson() {{
          put("updateWidget", new GenericJson() {{
            put("selectionInputWidgetSuggestions", new GenericJson() {{
              put("suggestions", suggestions);
            }});
          }});
        }}));
      }});
    }};
  }

  /**
   * Generate a suggested contact given an ID.
   * 
   * @param id The ID of the contact to return.
   * @return The contact formatted as a selection item in the menu.
   */
  GoogleAppsCardV1SelectionItem getSuggestedContact(String id) {
    return new GoogleAppsCardV1SelectionItem()
      .setValue(id)
      .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
      .setText("Contact " + id);
  }
}

מחליפים את FUNCTION_URL בנקודת הקצה של HTTP ששולחת שאילתה למקור הנתונים החיצוני.

Apps Script

בדוגמה הזו, הודעת הכרטיס נשלחת על ידי החזרת JSON של הכרטיס. אפשר גם להשתמש בשירות הכרטיסים של Apps Script.

apps-script/chat/selection-input/selection-input.gs
/**
* Responds to a Message trigger in Google Chat.
*
* @param {Object} event the event object from Google Chat
* @return {Object} Response from the Chat app.
*/
function onMessage(event) {
  // Replies with a card that contains the multiselect menu.
  return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
    cardsV2: [{
      cardId: "contactSelector",
      card: { sections:[{ widgets: [{
        selectionInput: {
          name: "contacts",
          type: "MULTI_SELECT",
          label: "Selected contacts",
          multiSelectMaxSelectedItems: 3,
          multiSelectMinQueryLength: 1,
          externalDataSource: { function: "queryContacts" },
          // Suggested items loaded by default.
          // The list is static here but it could be dynamic.
          items: [getSuggestedContact("3")]
        }
      }]}]}
    }]
  }}}}};
}

/**
* Get contact suggestions based on text typed by users.
*
* @param {Object} event the event object that contains the user's query
* @return {Object} suggestions
*/
function queryContacts(event) {
  const query = event.commonEventObject.parameters["autocomplete_widget_query"];
  return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
    // The list is static here but it could be dynamic.
    getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
  // Only return items based on the query from the user.
  ].filter(e => !query || e.text.includes(query)) }}}]}};
}

/**
* Generate a suggested contact given an ID.
*
* @param {String} id The ID of the contact to return.
* @return {Object} The contact formatted as a selection item in the menu.
*/
function getSuggestedContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

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

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

הגדרת ווידג'טים נדרשים לפעולות

כחלק מהכרטיס action, מוסיפים את שמות הווידג'טים שנדרשים לפעולה לרשימה requiredWidgets.

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

כשמגדירים "all_widgets_are_required": "true" לפעולה, הפעולה הזו מחייבת את כל הווידג'טים בכרטיס.

הגדרת פעולה של all_widgets_are_required בבחירה מרובה

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "value": "contact-1",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 1",
                "bottomText": "Contact one description",
                "selected": false
              },
              {
                "value": "contact-2",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 2",
                "bottomText": "Contact two description",
                "selected": false
              },
              {
                "value": "contact-3",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 3",
                "bottomText": "Contact three description",
                "selected": false
              },
              {
                "value": "contact-4",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 4",
                "bottomText": "Contact four description",
                "selected": false
              },
              {
                "value": "contact-5",
                "startIconUri": "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
                "text": "Contact 5",
                "bottomText": "Contact five description",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}
הגדרת פעולת all_widgets_are_required ב-dateTimePicker

JSON

{
  "sections": [
    {
      "widgets": [
        {
          "textParagraph": {
            "text": "A datetime picker widget with both date and time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_and_time",
            "label": "meeting",
            "type": "DATE_AND_TIME"
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just date:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_date_only",
            "label": "Choose a date",
            "type": "DATE_ONLY",
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textParagraph": {
            "text": "A datetime picker widget with just time:"
          }
        },
        {
          "divider": {}
        },
        {
          "dateTimePicker": {
            "name": "date_time_picker_time_only",
            "label": "Select a time",
            "type": "TIME_ONLY"
          }
        }
      ]
    }
  ]
}
הגדרת פעולה בתפריט הנפתח all_widgets_are_required

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "all_widgets_are_required": true
            },
            "items": [
              {
                "text": "Red",
                "value": "red",
                "selected": false
              },
              {
                "text": "Green",
                "value": "green",
                "selected": false
              },
              {
                "text": "White",
                "value": "white",
                "selected": false
              },
              {
                "text": "Blue",
                "value": "blue",
                "selected": false
              },
              {
                "text": "Black",
                "value": "black",
                "selected": false
              }
            ]
          }
        }
      ]
    }
  ]
}

הגדרת האימות של ווידג'ט להזנת טקסט

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

הגדרת מגבלת תווים לווידג'ט של קלט טקסט

JSON

{
  "sections": [
    {
      "header": "Tell us about yourself",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "favoriteColor",
            "label": "Favorite color",
            "type": "SINGLE_LINE",
            "validation": {"character_limit":15},
            "onChangeAction":{
              "all_widgets_are_required": true
            }
          }
        }
      ]
    }
  ]
}
הגדרת סוג הקלט בווידג'ט של קלט טקסט

JSON

{
  "sections": [
    {
      "header": "Validate text inputs by input types",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 2,
      "widgets": [
        {
          "textInput": {
            "name": "mailing_address",
            "label": "Please enter a valid email address",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "EMAIL"
            },
            "onChangeAction": {
              "all_widgets_are_required": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "input_type": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "input_type": "FLOAT"
            }
          }
        }
      ]
    }
  ]
}

פתרון בעיות

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

יכול להיות שלא תוצג הודעת שגיאה בממשק המשתמש של Chat, אבל אם הפעלתם את רישום השגיאות באפליקציות של Chat, תוכלו לראות הודעות שגיאה תיאוריות ונתוני יומן שיעזרו לכם לתקן את השגיאות. לקבלת עזרה בצפייה בשגיאות, באיתור באגים ובתיקון שגיאות, אפשר לעיין במאמר פתרון בעיות ותיקון שגיאות ב-Google Chat.

אפליקציות ל-Chat שאינן תוספים: עיצוב כרטיסים ודיאלוגים אינטראקטיביים

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

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

Node.js

node/selection-input/index.js
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

Python

python/selection-input/main.py
'selectionInput': {
  'name': "contacts",
  'type': "MULTI_SELECT",
  'label': "Selected contacts",
  'multiSelectMaxSelectedItems': 3,
  'multiSelectMinQueryLength': 1,
  'externalDataSource': { 'function': "getContacts" },
  # Suggested items loaded by default.
  # The list is static here but it could be dynamic.
  'items': [get_contact("3")]
}

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
  .setName("contacts")
  .setType("MULTI_SELECT")
  .setLabel("Selected contacts")
  .setMultiSelectMaxSelectedItems(3)
  .setMultiSelectMinQueryLength(1)
  .setExternalDataSource(new GoogleAppsCardV1Action().setFunction("getContacts"))
  .setItems(List.of(getContact("3")))))))))));

Apps Script

apps-script/selection-input/selection-input.gs
selectionInput: {
  name: "contacts",
  type: "MULTI_SELECT",
  label: "Selected contacts",
  multiSelectMaxSelectedItems: 3,
  multiSelectMinQueryLength: 1,
  externalDataSource: { function: "getContacts" },
  // Suggested items loaded by default.
  // The list is static here but it could be dynamic.
  items: [getContact("3")]
}

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

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

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

Node.js

node/selection-input/index.js
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}

Python

python/selection-input/main.py
def on_widget_update(event: dict) -> dict:
  """Responds to a WIDGET_UPDATE event in Google Chat."""
  if "getContacts" == event.get("common").get("invokedFunction"):
    query = event.get("common").get("parameters").get("autocomplete_widget_query")
    return { 'actionResponse': {
      'type': "UPDATE_WIDGET",
      'updatedWidget': { 'suggestions': { 'items': list(filter(lambda e: query is None or query in e["text"], [
        # The list is static here but it could be dynamic.
        get_contact("1"), get_contact("2"), get_contact("3"), get_contact("4"), get_contact("5")
      # Only return items based on the query from the user
      ]))}}
    }}


def get_contact(id: str) -> dict:
  """Generate a suggested contact given an ID."""
  return {
    'value': id,
    'startIconUri': "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    'text': "Contact " + id
  }

Java

java/selection-input/src/main/java/com/google/chat/selectionInput/App.java
// Responds to a WIDGET_UPDATE event in Google Chat.
Message onWidgetUpdate(JsonNode event) {
  if ("getContacts".equals(event.at("/invokedFunction").asText())) {
    String query = event.at("/common/parameters/autocomplete_widget_query").asText();
    return new Message().setActionResponse(new ActionResponse()
      .setType("UPDATE_WIDGET")
      .setUpdatedWidget(new UpdatedWidget()
        .setSuggestions(new SelectionItems().setItems(List.of(
          // The list is static here but it could be dynamic.
          getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
        // Only return items based on the query from the user
        ).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList()))));
  }
  return null;
}

// Generate a suggested contact given an ID.
GoogleAppsCardV1SelectionItem getContact(String id) {
  return new GoogleAppsCardV1SelectionItem()
    .setValue(id)
    .setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
    .setText("Contact " + id);
}

Apps Script

apps-script/selection-input/selection-input.gs
/**
 * Responds to a WIDGET_UPDATE event in Google Chat.
 *
 * @param {Object} event The event object from Chat API.
 * @return {Object} Response from the Chat app.
 */
function onWidgetUpdate(event) {
  if (event.common["invokedFunction"] === "getContacts") {
    const query = event.common.parameters["autocomplete_widget_query"];
    return { actionResponse: {
      type: "UPDATE_WIDGET",
      updatedWidget: { suggestions: { items: [
        // The list is static here but it could be dynamic.
        getContact("1"), getContact("2"), getContact("3"), getContact("4"), getContact("5")
      // Only return items based on the query from the user
      ].filter(e => !query || e.text.includes(query))}}
    }};
  }
}

/**
 * Generate a suggested contact given an ID.
 *
 * @param {String} id The ID of the contact to return.
 * @return {Object} The contact formatted as a suggested item for selectors.
 */
function getContact(id) {
  return {
    value: id,
    startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
    text: "Contact " + id
  };
}