更新配置卡

本指南介绍了如何动态更新配置卡片以响应用户操作,例如点击按钮或从菜单中选择项。

您可以插入、移除或替换卡片部分和 widget,以创建可根据用户输入进行调整的自适应界面。例如,您可以更新卡片界面,以便向在需要输入整数的输入字段中输入字符串的用户显示验证错误消息;然后,在用户输入整数后,再次更新该界面以隐藏该消息。

修改卡片部分和微件

如需更新卡片,您的 widget 操作(例如 onClickAction)必须调用一个返回 RenderActions 对象(包含修改指令)的函数。

在卡片部分,您可以进行以下修改:

  • 插入部分:使用 setInsertSection 将部分添加到卡片的顶部或 ID 指定的现有部分下方。
  • 移除某个部分:使用 setRemoveSection 移除 ID 指定的部分。
  • 替换某个部分:使用由 ID 指定的新部分替换现有部分,其中 ID 为 setReplaceSection

在卡片 widget 上,您可以进行以下修改:

  • 插入 widget:使用 setInsertWidget 在 ID 指定的现有 widget 之前或之后添加 widget。
  • 移除 widget:使用 setRemoveWidget 移除 ID 指定的 widget。
  • 替换 widget:使用具有 setReplaceWidget 的新 widget 替换现有 widget。

示例:插入和移除版块及 widget

以下示例构建了一个包含按钮的配置卡片,这些按钮可调用函数来修改卡片,方法是插入或移除 widget 和部分。

JSON

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "addOns": {
    "common": {
      "name": "Test Project",
      "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png",
      "useLocaleFromApp": true
    },
    "flows": {
      "workflowElements": [
        {
          "id": "modify_card",
          "state": "ACTIVE",
          "name": "Modify Card",
          "workflowAction": {
            "inputs": [
              {
                "id": "value1",
                "description": "The first input",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              },
              {
                "id": "value2",
                "description": "The second number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              },
              {
                "id": "value3",
                "description": "The third number",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "outputs": [
              {
                "id": "result",
                "description": "Modify Card result",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "onConfigFunction": "onConfig",
            "onExecuteFunction": "onExecute"
          }
        }
      ]
    }
  }
}

Apps 脚本

// Return a configuration Card for the step.
function onConfig() {

  const textInput_1 = CardService.newTextInput()
    .setFieldName("value1")
    .setTitle("First Value!")
    .setId("text_input_1")
    .setHostAppDataSource(
      CardService.newHostAppDataSource()
        .setWorkflowDataSource(
          CardService.newWorkflowDataSource()
            .setIncludeVariables(true)
        )
    );

  const textInput_2 = CardService.newTextInput()
    .setFieldName("value2")
    .setTitle("Second Value!")
    .setId("text_input_2")
    .setHostAppDataSource(
      CardService.newHostAppDataSource()
        .setWorkflowDataSource(
          CardService.newWorkflowDataSource()
            .setIncludeVariables(true)
        )
    );

  // Create buttons that call functions to modify the card.
  const buttonSet = CardService.newButtonSet()
    .setId("card_modification_buttons")
    .addButton(
      CardService.newTextButton()
        .setAltText("Insert Card Section")
        .setText("Insert Card Section")
        .setOnClickAction(
          CardService.newAction()
            .setFunctionName('insertSection')
        )
    )
    .addButton(
      CardService.newTextButton()
        .setAltText("Insert Text Widget")
        .setText("Insert Text Widget")
        .setOnClickAction(
          CardService.newAction()
            .setFunctionName('insertWidget')
        )
    );

  var firstSection =
    CardService.newCardSection()
      .setId("card_section_1")
      .addWidget(textInput_1)
      .addWidget(textInput_2)
      .addWidget(buttonSet);

  var card = CardService.newCardBuilder()
    .addSection(firstSection)
    .build();

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

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

  return AddOnsResponseService.newRenderActionBuilder()
    .setAction(action)
    .build();
}
// If the step runs, return output variables.
function onExecute(event) {
  var value1 = event.workflow.actionInvocation.inputs["value1"].stringValues[0];
  var value2 = event.workflow.actionInvocation.inputs["value2"].stringValues[0];
  var value3; // Declare value3
  var result;
  // Check if the "value3" key exists in the inputs, which only exists if
  // the third text input is inserted to the Card.
  if (event.workflow.actionInvocation.inputs["value3"]) {
    value3 = event.workflow.actionInvocation.inputs["value3"].stringValues[0];

    result = value1 + "\n" + value2 + "\n" + value3;
  } else {
    result = value1 + "\n" + value2;
  }
  // Output the file ID through a variableData, which can be used by
  // later steps in a workflow.
  const variableData = AddOnsResponseService.newVariableData()
    .addStringValue(result);

  let textFormatElement = AddOnsResponseService.newTextFormatElement()
    .setText("Output: " + JSON.stringify(variableData));

  let workflowTextFormat = AddOnsResponseService.newWorkflowTextFormat()
    .addTextFormatElement(textFormatElement);

  // The string key for each variableData must match with the IDs of the
  // outputs defined in the manifest.
  let returnAction = AddOnsResponseService.newReturnOutputVariablesAction()
    .setVariables({ "result": variableData })
    .setLog(workflowTextFormat);

  let hostAppAction = AddOnsResponseService.newHostAppAction()
    .setWorkflowAction(returnAction);

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

/**
* Inserts a new CardSection with several widgets.
*/
function insertSection(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const optionalSection = CardService.newCardSection().setId("card_section_2")
    .addWidget(
      CardService.newTextParagraph()
        .setText("This is a text paragraph inside the new card section")
    )
    .addWidget(
      CardService.newButtonSet()
        .addButton(
          CardService.newTextButton()
            .setText("Remove Card Section")
            .setOnClickAction(
              CardService.newAction()
                .setFunctionName("removeSection")
            )
        )
    )
    .addWidget(
      CardService.newButtonSet()
        .addButton(
          CardService.newTextButton()
            .setText("Replace Card Section")
            .setOnClickAction(
              CardService.newAction()
                .setFunctionName("replaceSection")
            )
        )
    );

  // Insert the new section beneath section "card_section_1".
  // You can also insert at the top of a Card.
  const sectionInsertion = AddOnsResponseService.newInsertSection()
    .insertBelowSection("card_section_1")
    .setSection(optionalSection);

  const modifyAction = AddOnsResponseService.newAction()
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setInsertSection(sectionInsertion)
    );

  return AddOnsResponseService.newRenderActionBuilder()
    .setAction(modifyAction)
    .build();
}
/**
* Replaces an existing CardSection with a new CardSection with the same ID.
*/
function replaceSection(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const replacementSection = CardService.newCardSection()
    .setId("card_section_2")
    .addWidget(
      CardService.newTextParagraph()
        .setText("Card Section replaced!")
    )
    .addWidget(
      CardService.newButtonSet()
        .addButton(
          CardService.newTextButton()
            .setText("Remove Card Section")
            .setOnClickAction(
              CardService.newAction()
                .setFunctionName("removeSection")
            )
        )
    );

  const modifyAction = AddOnsResponseService.newAction()
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setReplaceSection(replacementSection)
    );

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

/**
* Replaces an existing CardWidget with a new CardWidget with the same ID.
*/
function replaceWidget(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const replacementWidget = CardService.newTextParagraph()
    .setText("This is a replacement widget!")
    .setId("text_input_3");

  const modifyAction = AddOnsResponseService.newAction()
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setReplaceWidget(replacementWidget)
    );

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

/**
* Inserts an additional text input widget and a button that can remove it.
*/
function insertWidget(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const buttonSet = CardService.newButtonSet()
    .setId("widget_1")
    .addButton(
      CardService.newTextButton()
        .setAltText("Remove Widget")
        .setText("Remove Widget")
        .setOnClickAction(
          CardService.newAction()
            .setFunctionName('removeWidget')
        ))
    .addButton(
      CardService.newTextButton()
        .setAltText("Replace Widget")
        .setText("Replace Widget")
        .setOnClickAction(
          CardService.newAction()
            .setFunctionName('replaceWidget')
        ));

  const textInput_3 = CardService.newTextInput()
    .setFieldName("value3")
    .setTitle("Third Value")
    .setId("text_input_3");

  // Widgets can be inserted either before or after another widget.
  // This example inserts a button below a text input, then inserts
  // another text input between the existing text input and the new button.
  const buttonSetInsertion = AddOnsResponseService.newInsertWidget()
    .insertBelowWidget("text_input_2")
    .setWidget(buttonSet);

  const textInputInsertion = AddOnsResponseService.newInsertWidget()
    .insertAboveWidget("widget_1")
    .setWidget(textInput_3);

  const modifyAction = AddOnsResponseService.newAction()
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setInsertWidget(buttonSetInsertion)
    )
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setInsertWidget(textInputInsertion)
    );

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

/**
* Removes one or more existing widgets by ID.
*/
function removeWidget(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const textInputDeletion = AddOnsResponseService.newRemoveWidget()
    .setWidgetId("text_input_3");
  const buttonSetDeletion = AddOnsResponseService.newRemoveWidget()
    .setWidgetId("widget_1");

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

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

/**
* Removes an existing card section by ID.
*/
function removeSection(event) {
  console.log("event: " + JSON.stringify(event, null, 3));

  const sectionDeletion = AddOnsResponseService.newRemoveSection()
    .setSectionId('card_section_2');

  const modifyAction = AddOnsResponseService.newAction()
    .addModifyCard(
      AddOnsResponseService.newModifyCard()
        .setRemoveSection(sectionDeletion)
    );

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