ตรวจสอบตัวแปรอินพุต

คู่มือนี้อธิบายวิธียืนยันตัวแปรอินพุต

เมื่อกำหนดตัวแปรอินพุต แนวทางปฏิบัติแนะนำคือให้ยืนยันว่าผู้ใช้ป้อนค่าที่เหมาะสม ตัวอย่างเช่น หากคุณขอให้ผู้ใช้ป้อนตัวเลข การยืนยันว่าผู้ใช้ป้อน 1 แทน a จะยืนยันว่าขั้นตอนของคุณ ทำงานโดยไม่มีข้อผิดพลาด

การยืนยันตัวแปรอินพุตทำได้ 2 วิธีดังนี้

  • การยืนยันฝั่งไคลเอ็นต์: การยืนยันฝั่งไคลเอ็นต์ช่วยให้คุณยืนยันอินพุตของผู้ใช้ได้โดยตรงใน อุปกรณ์ของผู้ใช้ ผู้ใช้จะได้รับความคิดเห็นทันทีและสามารถแก้ไขข้อผิดพลาดในอินพุตได้ขณะกำหนดค่าขั้นตอน
  • การยืนยันฝั่งเซิร์ฟเวอร์: การยืนยันฝั่งเซิร์ฟเวอร์ช่วยให้คุณเรียกใช้ตรรกะในเซิร์ฟเวอร์ระหว่างการยืนยัน ซึ่งมีประโยชน์เมื่อคุณต้องการค้นหาข้อมูลที่ไคลเอ็นต์ไม่มี เช่น ข้อมูลในระบบหรือฐานข้อมูลอื่นๆ

การยืนยันฝั่งไคลเอ็นต์

การยืนยันฝั่งไคลเอ็นต์ทำได้ 2 วิธีดังนี้

  • สำหรับการยืนยันพื้นฐาน เช่น การยืนยันว่าวิดเจ็ตมีอักขระไม่เกินจำนวนที่กำหนดหรือมีสัญลักษณ์ @ ให้เรียกใช้คลาส 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 ภายในการ์ดปัจจุบัน
    • 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 เพื่อตรวจสอบว่าอินพุตข้อความ 2 รายการเท่ากันหรือไม่ ระบบจะแสดงข้อความแสดงข้อผิดพลาดหากอินพุตไม่เท่ากัน

  • เมื่อตรงตาม failCondition (อินพุตไม่เท่ากัน) ระบบจะตั้งค่าเครื่องมือข้อความแสดงข้อผิดพลาดเป็น VISIBLE และแสดงขึ้น
    รูปที่ 1: เมื่อตรงตาม failCondition (อินพุตไม่เท่ากัน) ระบบจะตั้งค่าวิดเจ็ตข้อความแสดงข้อผิดพลาด เป็น VISIBLE และวิดเจ็ตจะปรากฏขึ้น
  • เมื่อตรงตาม successCondition (อินพุตเท่ากัน) ระบบจะตั้งค่าเครื่องมือข้อความแสดงข้อผิดพลาดเป็น HIDDEN และจะไม่ปรากฏ
    รูปที่ 2: เมื่อตรงตาม successCondition (อินพุตเท่ากัน) ระบบจะตั้งค่าวิดเจ็ตข้อความแสดงข้อผิดพลาดเป็น HIDDEN และวิดเจ็ตจะไม่ปรากฏขึ้น

ตัวอย่างโค้ดของแอปพลิเคชันและไฟล์ Manifest 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();
}

ไฟล์ Manifest 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 2 ตัว
    • -: ลบตัวเลข int64, uint64 หรือ double 2 ตัว
    • *: คูณตัวเลข int64, uint64, หรือ double 2 ตัว
    • /: หารตัวเลข int64, uint64, หรือ double 2 ตัว (การหารจำนวนเต็ม)
    • %: คำนวณค่ามอดุโลของตัวเลข int64 หรือ uint64 2 ตัว
    • -: เปลี่ยนตัวเลข int64 หรือ uint64 เป็นค่าลบ
  • การดำเนินการทางตรรกะ:
    • &&: ดำเนินการ AND ทางตรรกะกับค่าบูลีน 2 ค่า
    • ||: ดำเนินการ OR ทางตรรกะกับค่าบูลีน 2 ค่า
    • !: ดำเนินการ NOT ทางตรรกะกับค่าบูลีน
  • การดำเนินการเปรียบเทียบ:
    • ==: ตรวจสอบว่าค่า 2 ค่าเท่ากันหรือไม่ รองรับตัวเลขและรายการ
    • !=: ตรวจสอบว่าค่า 2 ค่าไม่เท่ากันหรือไม่ รองรับตัวเลขและรายการ
    • <: ตรวจสอบว่าตัวเลข int64, uint64 หรือ double ตัวแรกน้อยกว่าตัวที่ 2 หรือไม่
    • <=: ตรวจสอบว่าตัวเลข int64, uint64 หรือ double ตัวแรกน้อยกว่าหรือเท่ากับตัวที่ 2 หรือไม่
    • >: ตรวจสอบว่าตัวเลข int64, uint64 หรือ double ตัวแรกมากกว่าตัวที่ 2 หรือไม่
    • >=: ตรวจสอบว่าตัวเลข int64, uint64 หรือ double ตัวแรกมากกว่าหรือเท่ากับตัวที่ 2 หรือไม่
  • การดำเนินการกับรายการ:
    • in: ตรวจสอบว่ามีค่าอยู่ในรายการหรือไม่ รองรับตัวเลข สตริง และรายการที่ซ้อนกัน
    • size: แสดงผลจำนวนรายการในรายการ รองรับตัวเลขและรายการที่ซ้อนกัน

สถานการณ์การยืนยัน CEL ที่ไม่รองรับ

  • ขนาดอาร์กิวเมนต์ไม่ถูกต้องสำหรับการดำเนินการแบบไบนารี: การดำเนินการแบบไบนารี (เช่น add_int64, เท่ากับ) ต้องใช้อาร์กิวเมนต์ 2 รายการพอดี การระบุจำนวนอาร์กิวเมนต์ที่แตกต่างกันจะทำให้เกิดข้อผิดพลาด
  • ขนาดอาร์กิวเมนต์ไม่ถูกต้องสำหรับการดำเนินการแบบยูนิแทรี: การดำเนินการแบบยูนิแทรี (เช่น negate_int64) ต้องใช้อาร์กิวเมนต์ 1 รายการพอดี การระบุจำนวนอาร์กิวเมนต์ที่แตกต่างกันจะทำให้เกิดข้อผิดพลาด
  • ประเภทที่ไม่รองรับในการดำเนินการทางตัวเลข: การดำเนินการทางตัวเลขแบบไบนารีและแบบยูนิแทรีจะยอมรับเฉพาะอาร์กิวเมนต์ตัวเลขเท่านั้น การระบุประเภทอื่นๆ (เช่น บูลีน) จะทำให้เกิดข้อผิดพลาด

การตรวจสอบฝั่งเซิร์ฟเวอร์

การยืนยันฝั่งเซิร์ฟเวอร์ช่วยให้คุณเรียกใช้ตรรกะฝั่งเซิร์ฟเวอร์ได้โดยการระบุ onSaveFunction() ในโค้ดของขั้นตอน เมื่อผู้ใช้นำทางออกจากบัตรการกำหนดค่าของขั้นตอน onSaveFunction() จะทำงานและช่วยให้คุณยืนยันอินพุตของผู้ใช้ได้

หากอินพุตของผู้ใช้ถูกต้อง ให้แสดงผล saveWorkflowAction

หากอินพุตของผู้ใช้ไม่ถูกต้อง ให้แสดงผลบัตรการกำหนดค่าที่แสดงข้อความแสดงข้อผิดพลาดแก่ผู้ใช้ซึ่งอธิบายวิธีแก้ไขข้อผิดพลาด

เนื่องจากการตรวจสอบฝั่งเซิร์ฟเวอร์เป็นการดำเนินการแบบอะซิงโครนัส ผู้ใช้อาจไม่ทราบข้อผิดพลาดของอินพุตจนกว่าจะเผยแพร่โฟลว์

id ของอินพุตแต่ละรายการที่ผ่านการยืนยันในไฟล์ Manifest ต้องตรงกับ name ของวิดเจ็ตการ์ดในโค้ด

ตัวอย่างต่อไปนี้จะยืนยันว่าอินพุตข้อความของผู้ใช้มีเครื่องหมาย "@"

ไฟล์ Manifest

ข้อมูลบางส่วนของไฟล์ Manifest จะระบุ onSaveFunction() ที่ชื่อ "onSave"

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();
  }
}