אימות משתנה קלט

במדריך הזה מוסבר איך לאמת משתנה קלט.

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

יש שתי דרכים לאמת משתנה קלט:

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

אימות בצד הלקוח

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

  • כדי לבצע אימות בסיסי, כמו אימות של ווידג'ט שמכיל פחות ממספר מסוים של תווים או שמכיל את הסמל @, מפעילים את המחלקה Validation של שירות הכרטיסים של תוסף Google Workspace.
  • כדי לבצע אימות חזק, כמו השוואה בין ערכי ווידג'טים, אפשר להוסיף אימות באמצעות Common Expression Language ‏ (CEL) לווידג'טים הבאים של כרטיסים שנתמכים באמצעות CardService.

הפעלת המחלקה Validation

בדוגמה הבאה מוודאים שווידג'ט TextInput מכיל 10 תווים או פחות:

Apps Script

const validation = CardService.newValidation().setCharacterLimit('10').setInputType(
    CardService.InputType.TEXT);

כדי להשתמש באפשרויות אימות נוספות, צריך להשתמש באימות CEL.

אימות CEL

אימות באמצעות Common Expression Language (CEL) מאפשר לבצע בדיקות מיידיות של קלט בלי ההשהיה שמאפיינת אימות בצד השרת. זאת באמצעות העברת בדיקות של ערכי קלט שלא תלויות באחזור נתונים משירותים אחרים לצד הלקוח.

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

כדי ליצור אימות CEL מלא, צריך להשתמש ברכיבים הבאים:

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

    • Id: מזהה ייחודי של ExpressionData בתוך Card הנוכחי.
    • Expression: מחרוזת CEL שמגדירה את לוגיקת האימות (למשל, "value1 == value2").
    • Conditions: רשימה של תנאים שמכילה מבחר של תוצאות אימות מוגדרות מראש (SUCCESS או FAILURE). התנאים קשורים ל-EventAction בצד הווידג'ט דרך Triggers עם actionRuleId משותף.
    • ברמת הכרטיס EventAction: מפעילים אימותים של CEL בכרטיס ומקשרים את השדה ExpressionData לווידג'טים של תוצאות באמצעות טריגרים של אחרי אירוע.
      • actionRuleId: מזהה ייחודי של EventAction.
      • ExpressionDataAction: הערך שמוגדר הוא START_EXPRESSION_EVALUATION כדי לציין שהפעולה הזו מתחילה את ההערכה של CEL.
      • Trigger: מחבר את Conditions לצד הווידג'ט EventActions על סמך actionRuleId.
  • EventAction ברמת הווידג'ט: קובעת את ההתנהגות של ווידג'ט התוצאות כשמתקיים תנאי ההצלחה או הכישלון. לדוגמה, ווידג'ט של תוצאה יכול להיות TextParagraph שמכיל הודעת שגיאה שמוצגת רק אם האימות נכשל.

    • actionRuleId: זהה ל-actionRuleId בצד הכרטיס Trigger.
    • CommonWidgetAction: מגדיר פעולות שלא כוללות הערכות, כמו עדכון של חשיפת הווידג'ט.
      • UpdateVisibilityAction: פעולה שמעדכנת את מצב החשיפה של הווידג'ט (VISIBLE או HIDDEN).

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

  • כשהתנאי failCondition מתקיים (הערכים של input לא שווים), הווידג'ט של הודעת השגיאה מוגדר ל-VISIBLE ומופיע.
    איור 1: כשמתקיים התנאי failCondition (הערכים של קלט 1 וקלט 2 לא שווים), הווידג'ט של הודעת השגיאה מוגדר ל-VISIBLE ומופיע.
  • כשמתקיים התנאי successCondition (הערכים של inputs זהים), הווידג'ט של הודעת השגיאה מוגדר כ-HIDDEN ולא מופיע.
    איור 2: כשמתקיים התנאי successCondition (הערכים של הקלט שווים), הווידג'ט של הודעת השגיאה מוגדר ל-HIDDEN ולא מוצג.

דוגמה לקוד האפליקציה ולקובץ המניפסט בפורמט JSON:

Apps Script

function onConfig() {
  // Create a Card
  let cardBuilder = CardService.newCardBuilder();

  const textInput_1 = CardService.newTextInput()
    .setTitle("Input field 1")
    .setFieldName("value1"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
  const textInput_2 = CardService.newTextInput()
    .setTitle("Input field 2")
    .setFieldName("value2"); // FieldName's value must match a corresponding ID defined in the inputs[] array in the manifest file.
  let sections = CardService.newCardSection()
    .setHeader("Enter same values for the two input fields")
    .addWidget(textInput_1)
    .addWidget(textInput_2);

  // CEL Validation

  // Define Conditions
  const condition_success = CardService.newCondition()
    .setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
    .setExpressionDataCondition(
      CardService.newExpressionDataCondition()
      .setConditionType(
        CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_SUCCESS));
  const condition_fail = CardService.newCondition()
    .setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
    .setExpressionDataCondition(
      CardService.newExpressionDataCondition()
      .setConditionType(
        CardService.ExpressionDataConditionType.EXPRESSION_EVALUATION_FAILURE));

  // Define Card-side EventAction
  const expressionDataAction = CardService.newExpressionDataAction()
    .setActionType(
      CardService.ExpressionDataActionType.START_EXPRESSION_EVALUATION);
  // Define Triggers for each Condition respectively
  const trigger_success = CardService.newTrigger()
    .setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID");
  const trigger_failure = CardService.newTrigger()
    .setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID");

  const eventAction = CardService.newEventAction()
    .setActionRuleId("CEL_TEXTINPUT_EVALUATION_RULE_ID")
    .setExpressionDataAction(expressionDataAction)
    .addPostEventTrigger(trigger_success)
    .addPostEventTrigger(trigger_failure);

  // Define ExpressionData for the current Card
  const expressionData = CardService.newExpressionData()
    .setId("expData_id")
    .setExpression("value1 == value2") // CEL expression
    .addCondition(condition_success)
    .addCondition(condition_fail)
    .addEventAction(eventAction);
  card = card.addExpressionData(expressionData);

  // Create Widget-side EventActions and a widget to display error message
  const widgetEventActionFail = CardService.newEventAction()
    .setActionRuleId("CEL_TEXTINPUT_FAILURE_RULE_ID")
    .setCommonWidgetAction(
      CardService.newCommonWidgetAction()
      .setUpdateVisibilityAction(
        CardService.newUpdateVisibilityAction()
        .setVisibility(
          CardService.Visibility.VISIBLE)));
  const widgetEventActionSuccess = CardService.newEventAction()
    .setActionRuleId("CEL_TEXTINPUT_SUCCESS_RULE_ID")
    .setCommonWidgetAction(
      CardService.newCommonWidgetAction()
      .setUpdateVisibilityAction(
        CardService.newUpdateVisibilityAction()
        .setVisibility(
          CardService.Visibility.HIDDEN)));
  const errorWidget = CardService.newTextParagraph()
    .setText("<font color=\"#FF0000\"><b>Error:</b> Please enter the same values for both input fields.</font>")
    .setVisibility(CardService.Visibility.HIDDEN) // Initially hidden
    .addEventAction(widgetEventActionFail)
    .addEventAction(widgetEventActionSuccess);
  sections = sections.addWidget(errorWidget);

  card = card.addSection(sections);
  // Build and return the Card
  return card.build();
}

קובץ מניפסט בפורמט JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "CEL validation example",
      "logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
      "useLocaleFromApp": true
    },
    "flows": {
      "workflowElements": [
        {
          "id": "cel_validation_demo",
          "state": "ACTIVE",
          "name": "CEL Demo",
          "description": "Demonstrates CEL Validation",
          "workflowAction": {
            "inputs": [
              {
                "id": "value1",
                "description": "The first number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              },
              {
                "id": "value2",
                "description": "The second number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "onConfigFunction": "onConfig",
            "onExecuteFunction": "onExecute"
          }
        }
      ]
    }
  }
}

ווידג'טים ופעולות נתמכים של אימות CEL

ווידג'טים של כרטיסים שתומכים באימות CEL

הווידג'טים הבאים תומכים באימות CEL:

  • TextInput
  • SelectionInput
  • DateTimePicker

פעולות אימות נתמכות של CEL

  • פעולות אריתמטיות
    • +: מוסיפה שני מספרים int64,‏ uint64 או double.
    • -: מחסירה שני מספרים מסוג int64,‏ uint64 או double.
    • *: מכפילה שני מספרים מסוג int64,‏ uint64 או double.
    • /: מחלקת שני מספרים מסוג int64,‏ uint64 או double (חילוק של מספרים שלמים).
    • %: מחשבת את המודולו של שני מספרים מסוג int64 או uint64.
    • -: הופך את הערך של מספר int64 או uint64.
  • פעולות לוגיות:
    • &&: מבצעת פעולה לוגית AND על שני ערכים בוליאניים.
    • ||: מבצעת פעולה לוגית OR על שני ערכים בוליאניים.
    • !: מבצעת פעולה לוגית NOT על ערך בוליאני.
  • פעולות השוואה:
    • ==: בודקת אם שני ערכים שווים. יש תמיכה במספרים וברשימות.
    • !=: בדיקה אם שני ערכים לא שווים. יש תמיכה במספרים וברשימות.
    • <: בודקת אם המספר הראשון int64, uint64 או double קטן מהמספר השני.
    • <=: בודקת אם המספר הראשון int64, uint64 או double קטן מהמספר השני או שווה לו.
    • >: בודקת אם המספר הראשון int64, uint64 או double גדול מהמספר השני.
    • >=: הפונקציה בודקת אם המספר הראשון, int64, uint64 או double, גדול מהמספר השני או שווה לו.
  • הצגת רשימה של פעולות:
    • in: בודקת אם ערך מסוים מופיע ברשימה. יש תמיכה במספרים, במחרוזות וברשימות מקוננות.
    • size: מחזירה את מספר הפריטים ברשימה. יש תמיכה במספרים וברשימות מקוננות.

תרחישי אימות של CEL שלא נתמכים

  • גודלי ארגומנטים שגויים לפעולות בינאריות: פעולות בינאריות (לדוגמה, add_int64, equals) דורשות בדיוק שני ארגומנטים. אם תספקו מספר אחר של ארגומנטים, תוצג שגיאה.
  • גודלי ארגומנטים שגויים לפעולות אונריות: פעולות אונריות (לדוגמה, negate_int64) דורשות ארגומנט אחד בלבד. אם תספקו מספר אחר של ארגומנטים, תוצג שגיאה.
  • סוגים שלא נתמכים בפעולות מספריות: פעולות מספריות בינאריות ואונאריות מקבלות רק ארגומנטים מספריים. אם תספקו סוגים אחרים (לדוגמה, בוליאני), תופיע שגיאה.

אימות בצד השרת

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

אם הקלט של המשתמש תקין, מחזירים saveWorkflowAction.

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

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

כל ערך id מאומת בקובץ המניפסט צריך להיות זהה לערך name של ווידג'ט כרטיס בקוד.

בדוגמה הבאה מוודאים שקלט הטקסט של המשתמש כולל את התו '@':

קובץ מניפסט

קטע מקובץ המניפסט שמציין פונקציה בשם onSaveFunction():

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "Server-side validation example",
      "logoUrl": "https://www.gstatic.com/images/branding/productlogos/calculator_search/v1/web-24dp/logo_calculator_search_color_1x_web_24dp.png",
      "useLocaleFromApp": true
    },
    "flows": {
      "workflowElements": [
        {
          "id": "server_validation_demo",
          "state": "ACTIVE",
          "name": "Email address validation",
          "description": "Asks the user for an email address",
          "workflowAction": {
            "inputs": [
              {
                "id": "email",
                "description": "email address",
                "cardinality": "SINGLE",
                "required": true,
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "onConfigFunction": "onConfig",
            "onExecuteFunction": "onExecute",
            "onSaveFunction": "onSave"
          }
        }
      ]
    }
  }
}

קוד אפליקציה

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

Apps Script

// A helper method to push a card interface
function pushCard(card) {
  const navigation = AddOnsResponseService.newNavigation()
    .pushCard(card);

  const action = AddOnsResponseService.newAction()
    .addNavigation(navigation);

  return AddOnsResponseService.newRenderActionBuilder()
    .setAction(action)
    .build();
}

function onConfig() {
  const emailInput = CardService.newTextInput()
    .setFieldName("email")
    .setTitle("User e-mail")
    .setId("email");

  const saveButton = CardService.newTextButton()
    .setText("Save!")
    .setOnClickAction(
      CardService.newAction()
        .setFunctionName('onSave')
    )

  const sections = CardService.newCardSection()
    .setHeader("Server-side validation")
    .setId("section_1")
    .addWidget(emailInput)
    .addWidget(saveButton);

  let card = CardService.newCardBuilder()
    .addSection(sections)
    .build();

  return pushCard(card);
}

function onExecute(event) {
}

/**
* Validates user input asynchronously when the user
* navigates away from a step's configuration card.
*/
function onSave(event) {
  console.log(JSON.stringify(event, null, 2));

  // "email" matches the input ID specified in the manifest file.
  var email = event.formInputs["email"][0];

  console.log(JSON.stringify(email, null, 2));

  // Validate that the email address contains an "@" sign:
  if (email.includes("@")) {
    // If successfully validated, save and proceed.
    const hostAppAction = AddOnsResponseService.newHostAppAction()
      .setWorkflowAction(
        AddOnsResponseService.newSaveWorkflowAction()
      );

    const textDeletion = AddOnsResponseService.newRemoveWidget()
      .setWidgetId("errorMessage");

    const modifyAction = AddOnsResponseService.newAction()
      .addModifyCard(
        AddOnsResponseService.newModifyCard()
          .setRemoveWidget(textDeletion)
      );

    return AddOnsResponseService.newRenderActionBuilder()
      .setHostAppAction(hostAppAction)
      .setAction(modifyAction)
      .build();

  } else {
    // If the input is invalid, return a card with an error message

    const textParagraph = CardService.newTextParagraph()
      .setId("errorMessage")
      .setMaxLines(1)
      .setText("<font color=\"#FF0000\"><b>Error:</b> Email addresses must include the '@' sign.</font>");

    const emailInput = CardService.newTextInput()
      .setFieldName("email")
      .setTitle("User e-mail")
      .setId("email");

    const saveButton = CardService.newTextButton()
      .setText("Save!")
      .setOnClickAction(
        CardService.newAction().setFunctionName('onSave')
      )

    const sections = CardService.newCardSection()
      .setHeader("Server-side validation")
      .setId("section_1")
      .addWidget(emailInput)
      .addWidget(textParagraph) //Insert the error message
      .addWidget(saveButton);

    let card = CardService.newCardBuilder()
      .addSection(sections)
      .build();

    const navigation = AddOnsResponseService.newNavigation()
      .pushCard(card);

    const action = AddOnsResponseService.newAction()
      .addNavigation(navigation);

    const hostAppAction = AddOnsResponseService.newHostAppAction()
      .setWorkflowAction(
        AddOnsResponseService.newWorkflowValidationErrorAction()
          .setSeverity(AddOnsResponseService.ValidationErrorSeverity.CRITICAL)
      );

    return AddOnsResponseService.newRenderActionBuilder()
      .setHostAppAction(hostAppAction)
      .setAction(action)
      .build();
  }
}