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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

פעולה תיאור
Navigation.pushCard(Card) דחיפת כרטיס לערימה הנוכחית. לשם כך, קודם צריך ליצור את הכרטיס במלואו.
Navigation.popCard() הסרת קלף אחד מהחלק העליון של המקבץ. פעולה שווה ערך ללחיצה על החץ לאחור בשורת הכותרת של התוסף. הפעולה הזו לא מסירה כרטיסי root.
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();
  }