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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. יוצרים אובייקט Action ומשייכים אותו לפונקציית קריאה חוזרת שהגדרתם.
  2. מתקשרים אל פונקציית הטיפול בווידג'ט המתאימה של הווידג'ט כדי להגדיר את 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();
  }