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

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

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

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

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

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

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