ניווט בכרטיסים

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

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

ניווט בכרטיס בדף הבית

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

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

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

  • אם הערך בשדה DisplayStyle הוא REPLACE (ברירת המחדל), הכרטיס לפי הקשר (הכרטיס הכתום הכהה בדיאגרמה) מחליף את הכרטיס הנוכחי שמוצג. כך למעשה מפעילים ערימת כרטיסים חדשה לפי הקשר מעל ערימת הכרטיסים שאינם לפי הקשר, והכרטיס לפי הקשר הוא הכרטיס הרמה הבסיסית (root) של המחסנית לפי הקשר.
  • אם הערך בשדה DisplayStyle הוא PEEK, בממשק המשתמש מופיעה כותרת הצצה שמופיעה בחלק התחתון של סרגל הצד של התוסף, כשכבת-על לכרטיס הנוכחי. בכותרת ההצצה מוצגת השם של הכרטיס החדש, עם לחצנים לשליטה על האפשרויות ולהחליט אם להציג את הכרטיס החדש או לא. אם הם ילחצו על הלחצן View, הכרטיס יחליף את הכרטיס הנוכחי (כפי שמתואר למעלה ב-REPLACE).

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

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

הפעולות של Navigation שמתוארות בהמשך פועלות רק בכרטיסים מאותו הקשר. לדוגמה, popToRoot() מתוך כרטיס הקשרי פותח רק את כל שאר הכרטיסים לפי הקשר, ולא ישפיעו על הכרטיסים בדף הבית.

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

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

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

  1. יוצרים אובייקט Action ומשייכים אותו לפונקציית קריאה חוזרת שמגדירים.
  2. צריך לקרוא לפונקציית ה-handler של הווידג'ט המתאימה כדי להגדיר את Action באותו ווידג'ט.
  3. מטמיעים את פונקציית הקריאה החוזרת שמבצעת את הניווט. הפונקציה הזו מקבלת אובייקט של אירוע פעולה כארגומנט, וחייבת לבצע את הפעולות הבאות:
    1. כדי להגדיר את השינוי בכרטיס, צריך ליצור אובייקט Navigation. אובייקט Navigation יחיד יכול להכיל מספר שלבי ניווט, שמבוצעים לפי סדר ההוספה שלהם לאובייקט.
    2. יצירת אובייקט ActionResponse באמצעות המחלקה ActionResponseBuilder והאובייקט Navigation.
    3. מחזירים את ה-ActionResponse המובנה.

כשיוצרים פקדי ניווט, משתמשים בפונקציות האובייקטים הבאות של Navigation:

שימוש תיאור
Navigation.pushCard(Card) דוחף כרטיס לערימה הנוכחית. כדי לעשות את זה צריך קודם כל לבנות את הכרטיס.
Navigation.popCard() הסרת כרטיס אחד מהחלק העליון של הערימה. זהה ללחיצה על החץ לחזרה אחורה בשורת הכותרת של התוסף. פעולה זו לא מסירה כרטיסים בסיסיים.
Navigation.popToRoot() מסיר את כל הכרטיסים מהערימה, מלבד כרטיס הבסיס. למעשה מאפסים את מקבץ הכרטיסים הזה.
Navigation.popToNamedCard(String) פותח את הכרטיסים מהערימה עד שהם מגיעים לכרטיס עם השם הנתון או לכרטיס הבסיס של המחסנית. אתם יכולים להקצות שמות לכרטיסים באמצעות הפונקציה CardBuilder.setName(String).
Navigation.updateCard(Card) מתבצעת החלפה במקום של הכרטיס הנוכחי, כדי לרענן את התצוגה שלו בממשק המשתמש.

אם אירוע או אינטראקציה של משתמש אמורים לגרום לעיבוד מחדש של כרטיסים באותו הקשר, יש להשתמש בשיטות Navigation.pushCard(), Navigation.popCard() ו-Navigation.updateCard() כדי להחליף את הכרטיסים הקיימים. אם אירוע או אינטראקציה של משתמש אמורים לגרום לעיבוד מחדש של כרטיסים בהקשר אחר, כדאי להשתמש ב-ActionResponseBuilder.setStateChanged() כדי לאלץ הפעלה מחדש של התוסף בהקשרים האלה.

דוגמאות לניווט:

  • אם אינטראקציה או אירוע משנים את המצב של הכרטיס הנוכחי (לדוגמה, הוספת משימה לרשימת משימות), צריך להשתמש ב-updateCard().
  • אם אינטראקציה או אירוע מספקים פרטים נוספים או מבקשים מהמשתמש לבצע פעולה נוספת (למשל, לחיצה על שם של פריט כדי לראות פרטים נוספים, או לחיצה על לחצן ליצירת אירוע חדש ביומן), צריך להשתמש ב-pushCard() כדי להציג את הדף החדש ולאפשר למשתמש לצאת מהדף החדש באמצעות לחצן 'הקודם'.
  • אם אינטראקציה או אירוע מתעדכנים בכרטיס קודם (לדוגמה, עדכון שם של פריט מתצוגת הפרטים), משתמשים בסוגים כמו popCard(), popCard(), pushCard(previous) ו-pushCard(current) כדי לעדכן את הכרטיס הקודם ואת הכרטיס הנוכחי.

רענון הכרטיסים

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

סרגל הצד של תוספי Google Workspace

הפעולה הזו מתווספת באופן אוטומטי לכרטיסים שנוצרים על ידי פונקציות הטריגר של homepageTrigger או contextualTrigger, כפי שצוין בקובץ המניפסט של התוסף ('השורשים' של מקבץ הכרטיסים לפי הקשר או של מקבץ הכרטיסים שלא קשורים להקשר).

החזרת כרטיסים מרובים

דוגמה לכרטיס תוסף

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

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

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

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

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

דוגמה

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

  /**
   *  Create the top-level card, with buttons leading to each of three
   *  'children' cards, as well as buttons to backtrack and return to the
   *  root card of the stack.
   *  @return {Card}
   */
  function createNavigationCard() {
    // Create a button set with actions to navigate to 3 different
    // 'children' cards.
    var buttonSet = CardService.newButtonSet();
    for(var i = 1; i <= 3; i++) {
      buttonSet.addButton(createToCardButton(i));
    }

    // Build the card with all the buttons (two rows)
    var card = CardService.newCardBuilder()
        .setHeader(CardService.newCardHeader().setTitle('Navigation'))
        .addSection(CardService.newCardSection()
            .addWidget(buttonSet)
            .addWidget(buildPreviousAndRootButtonSet()));
    return card.build();
  }

  /**
   *  Create a button that navigates to the specified child card.
   *  @return {TextButton}
   */
  function createToCardButton(id) {
    var action = CardService.newAction()
        .setFunctionName('gotoChildCard')
        .setParameters({'id': id.toString()});
    var button = CardService.newTextButton()
        .setText('Card ' + id)
        .setOnClickAction(action);
    return button;
  }

  /**
   *  Create a ButtonSet with two buttons: one that backtracks to the
   *  last card and another that returns to the original (root) card.
   *  @return {ButtonSet}
   */
  function buildPreviousAndRootButtonSet() {
    var previousButton = CardService.newTextButton()
        .setText('Back')
        .setOnClickAction(CardService.newAction()
            .setFunctionName('gotoPreviousCard'));
    var toRootButton = CardService.newTextButton()
        .setText('To Root')
        .setOnClickAction(CardService.newAction()
            .setFunctionName('gotoRootCard'));

    // Return a new ButtonSet containing these two buttons.
    return CardService.newButtonSet()
        .addButton(previousButton)
        .addButton(toRootButton);
  }

  /**
   *  Create a child card, with buttons leading to each of the other
   *  child cards, and then navigate to it.
   *  @param {Object} e object containing the id of the card to build.
   *  @return {ActionResponse}
   */
  function gotoChildCard(e) {
    var id = parseInt(e.parameters.id);  // Current card ID
    var id2 = (id==3) ? 1 : id + 1;      // 2nd card ID
    var id3 = (id==1) ? 3 : id - 1;      // 3rd card ID
    var title = 'CARD ' + id;

    // Create buttons that go to the other two child cards.
    var buttonSet = CardService.newButtonSet()
      .addButton(createToCardButton(id2))
      .addButton(createToCardButton(id3));

    // Build the child card.
    var card = CardService.newCardBuilder()
        .setHeader(CardService.newCardHeader().setTitle(title))
        .addSection(CardService.newCardSection()
            .addWidget(buttonSet)
            .addWidget(buildPreviousAndRootButtonSet()))
        .build();

    // Create a Navigation object to push the card onto the stack.
    // Return a built ActionResponse that uses the navigation object.
    var nav = CardService.newNavigation().pushCard(card);
    return CardService.newActionResponseBuilder()
        .setNavigation(nav)
        .build();
  }

  /**
   *  Pop a card from the stack.
   *  @return {ActionResponse}
   */
  function gotoPreviousCard() {
    var nav = CardService.newNavigation().popCard();
    return CardService.newActionResponseBuilder()
        .setNavigation(nav)
        .build();
  }

  /**
   *  Return to the initial add-on card.
   *  @return {ActionResponse}
   */
  function gotoRootCard() {
    var nav = CardService.newNavigation().popToRoot();
    return CardService.newActionResponseBuilder()
        .setNavigation(nav)
        .build();
  }