Is there any we can connect with task module from html parsed adaptive card?

49 Views Asked by At

I'm using an HTML-rendered version of the adaptive card using these 2 packages in MS Teams.

  1. https://www.npmjs.com/package/adaptivecards (version 2.11.1)

  2. https://www.npmjs.com/package/adaptivecards-react (version 1.1.1)

  3. React 16

Previously I was using the Adaptive Cards as is and posting them in the MS Teams group chat or channels with the help of Teams Apps. Any button clicks events used to get captured in my task module microservice.

Now when I'm parsing and rendering the same adaptive card to an HTML format, how do I achieve the same results when an HTML button is clicked?

This is how I'm parsing the adaptive card to HTML

Card.tsx

import { AdaptiveCardUsingHostConfigContext } from "adaptivecards-react";
import React, { useState, useEffect } from "react";
import * as AC from "adaptivecards";
import { SubmitAction } from "./SubmitAction";
import { ExecuteAction } from "./ExecuteAction";

AC.GlobalRegistry.actions.register(SubmitAction.JsonTypeName, SubmitAction);
AC.GlobalRegistry.actions.register(ExecuteAction.JsonTypeName, ExecuteAction);

const Card = (props: { card: any }) => {
  const { card } = props;
  const [isLoading, setIsLoading] = useState(false);
  const [cardPayload, setCardPayload] = useState(card);

  useEffect(() => {
    // console.log("Card: ", JSON.stringify(cardPayload));
  }, [cardPayload]);

  const onActionSubmit = (e: any) => {
    console.log("SUBMIT");
    console.log("inputs", e.data, e.id);
  };

  const onExecuteAction = (e: any) => {
    const actionType = e.getJsonTypeName();
    const { verb } = e;

    console.log(`executing action for ${verb}`);

    let response;
    if (actionType === "Action.Execute") {
      setIsLoading(true);
      response = cardPayload;
      response.body = [
        ...cardPayload.body,
        {
          text: "This is a fresh dynamic detail!",
          wrap: true,
          type: "TextBlock",
        },
      ];
      // }

      setCardPayload(response);
      setIsLoading(false);
    }
  };

  return (
    <div className="Card">
      <AdaptiveCardUsingHostConfigContext
        payload={cardPayload}
        onActionSubmit={onActionSubmit}
        onExecuteAction={onExecuteAction}
      />
    </div>
  );
};

export default Card;
ExecuteAction.tsx
import React from "react";
import * as AC from "adaptivecards";
import Button from "../components/Button";
import { reactDomRender } from "./shared";

export class ExecuteAction extends AC.ExecuteAction {
  private internalRenderedElement: any;

  static readonly titleProperty = new AC.StringProperty(
    AC.Versions.v1_0,
    "title",
    true
  );

  getTitle(): string | undefined {
    return this.getValue(ExecuteAction.titleProperty);
  }

  get renderedElement(): HTMLElement | undefined {
    return this.internalRenderedElement;
  }

  render() {
    const element = reactDomRender(this.renderElement());
    this.internalRenderedElement = element;
  }

  private renderElement = (): JSX.Element => {
    return <Button label={this.getTitle()} onClick={() => this.execute()} />;
  };
}
SubmitAction.tsx
import React from "react";
import * as AC from "adaptivecards";
import Button from "../components/Button";
import { reactDomRender } from "./shared";

export class SubmitAction extends AC.SubmitAction {
  private internalRenderedElement: any;

  static readonly textProperty = new AC.StringProperty(
    AC.Versions.v1_3,
    "text",
    true
  );

  static readonly titleProperty = new AC.StringProperty(
    AC.Versions.v1_3,
    "title",
    true
  );

  static readonly dataProperty = new AC.PropertyDefinition(
    AC.Versions.v1_3,
    "data"
  );

  getTitle(): string | undefined {
    return this.getValue(SubmitAction.titleProperty);
  }

  getText(): string | undefined {
    return this.getValue(SubmitAction.textProperty);
  }

  getData(): string | undefined {
    return this.getValue(SubmitAction.dataProperty);
  }

  getInputs(): any {
    return this.getValue(SubmitAction.associatedInputsProperty);
  }

  getInputValues(): any {
    return this.parent?.getAllInputs().map((input) => {
      return { id: input.id, value: input.value };
    });
  }

  get renderedElement(): HTMLElement | undefined {
    return this.internalRenderedElement;
  }

  render() {
    const element = reactDomRender(this.renderElement());
    this.internalRenderedElement = element;
  }

  execute() {
    console.log("-->", this.getData());
    const inputs = this.validateInputs();
    console.log(inputs);
    if (inputs.length === 0) {
      console.log("inputs valid");
      const inputValues = this.getInputValues();
      console.log("input values", inputValues);
    } else {
      for (const input of inputs) {
        console.log("input id", input.id);
        console.log("isValid", input.isValid());
        this.render();
      }
    }
  }

  onExecuteAction(action: any) {
    console.log("onExecuteAction");
    action();
  }

  private renderElement = (): JSX.Element => {
    return <Button label={this.title} onClick={() => this.execute()} />;
  };
}

shared.tsx

import React from "react";
import * as ReactDOM from "react-dom";

export const reactDomRender = (
  reactElement: React.ReactElement
): HTMLElement | undefined => {
  const div = document.createElement("div");
  ReactDOM.render(reactElement, div);
  return div;
};
RenderCard.tsx
/* eslint-disable react/prop-types */
import React from "react";
import Card from "./components/Card";
import { Loader } from "@fluentui/react-northstar";

const RenderCard = (props) => {
  const { card } = props;

  if (card) {
    return (
      <div className="App">
        <div>
          <Card card={card} />
        </div>
      </div>
    );
  } else {
    return <Loader label="Please wait..." size="largest" />;
  }
};

export default RenderCard;
hostConfig.tsx
const hostConfig = () => {
  return {
    spacing: {
      small: 3,
      default: 8,
      medium: 20,
      large: 30,
      extraLarge: 40,
      padding: 10,
    },
    separator: {
      lineThickness: 1,
      lineColor: "#EEEEEE",
    },
    supportsInteractivity: true,
    fontTypes: {
      default: {
        fontFamily:
          "'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
        fontSizes: {
          small: 12,
          default: 14,
          medium: 17,
          large: 21,
          extraLarge: 26,
        },
        fontWeights: {
          lighter: 200,
          default: 400,
          bolder: 600,
        },
      },
      monospace: {
        fontFamily: "'Courier New', Courier, monospace",
        fontSizes: {
          small: 12,
          default: 14,
          medium: 17,
          large: 21,
          extraLarge: 26,
        },
        fontWeights: {
          lighter: 200,
          default: 400,
          bolder: 600,
        },
      },
    },
    containerStyles: {
      default: {
        backgroundColor: "#FFFFFF",
        foregroundColors: {
          default: {
            default: "#000000",
            subtle: "#767676",
          },
          accent: {
            default: "#0063B1",
            subtle: "#0063B1",
          },
          attention: {
            default: "#FF0000",
            subtle: "#DDFF0000",
          },
          good: {
            default: "#54a254",
            subtle: "#DD54a254",
          },
          warning: {
            default: "#c3ab23",
            subtle: "#DDc3ab23",
          },
        },
      },
      emphasis: {
        backgroundColor: "#F0F0F0",
        foregroundColors: {
          default: {
            default: "#000000",
            subtle: "#767676",
          },
          accent: {
            default: "#2E89FC",
            subtle: "#882E89FC",
          },
          attention: {
            default: "#FF0000",
            subtle: "#DDFF0000",
          },
          good: {
            default: "#54a254",
            subtle: "#DD54a254",
          },
          warning: {
            default: "#c3ab23",
            subtle: "#DDc3ab23",
          },
        },
      },
      accent: {
        foregroundColors: {
          default: {
            default: "#333333",
            subtle: "#EE333333",
          },
          dark: {
            default: "#000000",
            subtle: "#66000000",
          },
          light: {
            default: "#FFFFFF",
            subtle: "#33000000",
          },
          accent: {
            default: "pink",
            subtle: "#882E89FC",
          },
          attention: {
            default: "#cc3300",
            subtle: "#DDcc3300",
          },
          good: {
            default: "#54a254",
            subtle: "#DD54a254",
          },
          warning: {
            default: "#e69500",
            subtle: "#DDe69500",
          },
        },
      },
      good: {
        backgroundColor: "#CCFFCC",
        foregroundColors: {
          default: {
            default: "#333333",
            subtle: "#EE333333",
          },
          dark: {
            default: "#000000",
            subtle: "#66000000",
          },
          light: {
            default: "#FFFFFF",
            subtle: "#33000000",
          },
          accent: {
            default: "#2E89FC",
            subtle: "#882E89FC",
          },
          attention: {
            default: "#cc3300",
            subtle: "#DDcc3300",
          },
          good: {
            default: "#54a254",
            subtle: "#DD54a254",
          },
          warning: {
            default: "#e69500",
            subtle: "#DDe69500",
          },
        },
      },
    },
    imageSizes: {
      small: 40,
      medium: 80,
      large: 160,
    },
    actions: {
      maxActions: 6,
      spacing: "default",
      buttonSpacing: 10,
      showCard: {
        actionMode: "inline",
        inlineTopMargin: 8,
      },
      actionsOrientation: "vertical",
      actionAlignment: "stretch",
      iconSize: 50,
    },
    adaptiveCard: {
      allowCustomStyle: true,
    },
    imageSet: {
      imageSize: "medium",
      maxImageHeight: 100,
    },
    factSet: {
      title: {
        color: "default",
        size: "default",
        isSubtle: false,
        weight: "bolder",
        wrap: true,
        maxWidth: 150,
      },
      value: {
        color: "default",
        size: "default",
        isSubtle: false,
        weight: "default",
        wrap: true,
      },
      spacing: 8,
    },
  };
};

export default hostConfig;

And this is the gist of the code from the component where I'm using the RenderCard.tsx component

import { ProvidesHostConfigContext } from "adaptivecards-react";
import hostConfig from "./hostConfig";
import RenderCard from "./rendercard";

<ProvidesHostConfigContext hostConfig={hostConfig}>
    <RenderCard card={card} />
</ProvidesHostConfigContext>

Any help or nudge in the right direction will be very helpful.

Thank you

1

There are 1 best solutions below

0
On BEST ANSWER

You can handle the button click event in your app's JavaScript or Typescript code and invoke the task module using the Teams JavaScript client library.

Example of code of TeamsJS V1:

let taskInfo = {
    title: null,
    height: null,
    width: null,
    url: null,
    card: null,
    fallbackUrl: null,
    completionBotId: null,
};

taskInfo.url = "https://contoso.com/teamsapp/customform";
taskInfo.title = "Custom Form";
taskInfo.height = 510;
taskInfo.width = 430;
submitHandler = (err, result) => {
    console.log(`Submit handler - err: ${err}`);
    console.log(`Submit handler - result\rName: ${result.name}\rEmail: ${result.email}\rFavorite book: ${result.favoriteBook}`);
};
microsoftTeams.tasks.startTask(taskInfo, submitHandler);

Example Code of TeamsJS V2:

let taskInfo = {
    title: null,
    height: null,
    width: null,
    url: null,
    card: null,
    fallbackUrl: null,
    completionBotId: null,
};

taskInfo.url = "https://contoso.com/teamsapp/customform";
taskInfo.title = "Custom Form";
taskInfo.height = 510;
taskInfo.width = 430;
dialogResponse = (dialogResponse) => {
        console.log(`Submit handler - err: ${dialogResponse.err}`);
        alert("Result = " + JSON.stringify(dialogResponse.result) + "\nError = " + JSON.stringify(dialogResponse.err));
    };

 microsoftTeams.dialog.open(taskInfo, dialogResponse);

Ref Doc: https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/task-modules/task-modules-tabs?tabs=teamsjs1%2Cteamsjs3#example-of-invoking-a-task-module

Please refer below sample: https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-task-module