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

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

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

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

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


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

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

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

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

הוספת כפתור

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

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

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

הוספת כפתור שמפעיל פונקציה בהתאמה אישית

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

הוספת כפתור בסגנון Material Design

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

כדי להחיל את הסגנון של Material Design, אל תכללו את המאפיין "color".

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

כדי ללמוד איך לעבד את מה שהמשתמשים מזינים, קראו את המאמר איסוף ועיבוד מידע ממשתמשי Google Chat.

איסוף טקסט

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

הוספת מתג

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

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

אכלוס דינמי של תפריטים נפתחים

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

אתם יכולים לאכלס באופן דינמי פריטים בתפריט נפתח ממקורות נתונים ב-Google Workspace או ממקור נתונים חיצוני. כדי להשתמש במקורות נתונים דינמיים, צריך לציין את השדה dataSourceConfigs, שהוא מערך של אובייקטים מסוג 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",
            "dataSourceConfigs": [
              {
                "platformDataSource": {
                  "commonDataSource": "USER"
                },
                "minCharactersTrigger": 1
              }
            ]
          }
        }
      ]
    }
  ]
}
אכלוס פריטים ממקור נתונים חיצוני

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

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

JSON

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

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

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

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 שלו.

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

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

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

JSON

{
  "sections": [
    {
      "header": "Select contacts",
      "widgets": [
        {
          "selectionInput": {
            "type": "MULTI_SELECT",
            "label": "Selected contacts",
            "name": "contacts",
            "multiSelectMaxSelectedItems": 3,
            "multiSelectMinQueryLength": 1,
            "onChangeAction": {
              "allWidgetsAreRequired": 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
              }
            ]
          }
        }
      ]
    }
  ]
}
הגדרת פעולת allWidgetsAreRequired ב-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":{
              "allWidgetsAreRequired": 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"
          }
        }
      ]
    }
  ]
}
הגדרת פעולה של allWidgetsAreRequired בתפריט הנפתח

JSON

{
  "sections": [
    {
      "header": "Section Header",
      "collapsible": true,
      "uncollapsibleWidgetsCount": 1,
      "widgets": [
        {
          "selectionInput": {
            "name": "location",
            "label": "Select Color",
            "type": "DROPDOWN",
            "onChangeAction": {
              "allWidgetsAreRequired": 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": {"characterLimit": 15},
            "onChangeAction":{
              "allWidgetsAreRequired": 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": {
              "inputType": "EMAIL"
            },
            "onChangeAction": {
              "allWidgetsAreRequired": true
            }
          }
        },
        {
          "textInput": {
            "name": "validate_integer",
            "label": "Please enter a number",
              "type": "SINGLE_LINE",
            "validation": {
              "inputType": "INTEGER"
            }
          }
        },
        {
          "textInput": {
            "name": "validate_float",
            "label": "Please enter a number with a decimal",
            "type": "SINGLE_LINE",
            "validation": {
              "inputType": "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. הערך הזה מייצג את מה שהמשתמש מקליד בתפריט.

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