5

I've created an adaptive card(using json) in my chatbot that takes input from users. I want to add a button that enables the user to add a new text field every time the user clicks on the insert field. (i.e., the user can click on insert button to enter details of education (school, college etc.))

Can this be achieved in adaptive cards?

I also wanted to know, can adaptive cards be designed in any other language (excluding json)

Priya G
  • 63
  • 1
  • 5

2 Answers2

9

The easiest way to do this is with Action.ShowCard:

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "Input.Text",
      "placeholder": "Placeholder 1",
      "id": "text1"
    }
  ],
  "actions": [
    {
      "type": "Action.ShowCard",
      "title": "Add field",
      "card": {
        "type": "AdaptiveCard",
        "body": [
          {
            "type": "Input.Text",
            "placeholder": "Placeholder 2",
            "id": "text2"
          }
        ],
        "actions": [
          {
            "type": "Action.ShowCard",
            "title": "Add field",
            "card": {
              "type": "AdaptiveCard",
              "body": [
                {
                  "type": "Input.Text",
                  "placeholder": "Placeholder 3",
                  "id": "text3"
                }
              ],
              "actions": [
                {
                  "type": "Action.ShowCard",
                  "title": "Add field",
                  "card": {
                    "type": "AdaptiveCard",
                    "body": [
                      {
                        "type": "Input.Text",
                        "placeholder": "Placeholder 4",
                        "id": "text4"
                      }
                    ],
                    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
                  }
                }
              ],
              "$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
            }
          }
        ],
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
      }
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "version": "1.0"
}

Nested cards

You may not like the way that looks, but there is an alternative. Microsoft Teams allows you to update messages, so you can update the card with more input fields in response to a submit action. First, you'll need a way of saving state for your card so you can update the card's activity. In C# you can declare a state property accessor like this:

public IStatePropertyAccessor<Dictionary<string, (string ActivityId, int InputCount)>> InputCardStateAccessor { get; internal set; }

Then you can instantiate it like this:

InputCardStateAccessor = _conversationState.CreateProperty<Dictionary<string, (string, int)>>("cardState");

In Node.js you won't need to declare anything but you can instantiate it like this:

this.inputCardState = this.conversationState.createProperty('cardState');

You'll want a consistent way to generate your card that you can use when you send the card initially and when you update the card. I'm using the AdaptiveCards NuGet package in C#:

public static IActivity GenerateAdaptiveCardActivityWithInputs(int inputCount, object valueObject)
{
    var cardData = JObject.FromObject(valueObject);
    var cardId = Convert.ToString(cardData[KEYCARDID]);

    var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
    {
        Body = Enumerable.Range(0, inputCount).Select(i =>
        {
            var inputId = $"text{i}";

            return new AdaptiveTextInput
            {
                Id = inputId,
                Value = Convert.ToString(cardData[inputId]),
            };
        }).ToList<AdaptiveElement>(),
        Actions = new List<AdaptiveAction>
        {
            new AdaptiveSubmitAction
            {
                Title = "Add field",
                Data = new Dictionary<string, string>
                {
                    { KEYCARDID, cardId },
                    { KEYSUBMITACTIONID, ACTIONSUBMITADDFIELD },
                },
            },
            new AdaptiveSubmitAction
            {
                Title = "Submit",
            },
        },
    };

    return MessageFactory.Attachment(new Attachment(AdaptiveCard.ContentType, content: JObject.FromObject(card)));
}

Node.js:

generateAdaptiveCardActivityWithInputs(inputCount, cardData) {
    var cardId = cardData[KEYCARDID];
    var body = [];

    for (let i = 0; i < inputCount; i++) {
        var inputId = `text${i}`;
        body.push({
            type: "Input.Text",
            id: inputId,
            value: cardData[inputId]
        });
    }

    var card = {
        type: "AdaptiveCard",
        $schema: "http://adaptivecards.io/schemas/adaptive-card.json",
        version: "1.0",
        body,
        actions: [
            {
                type: "Action.Submit",
                title: "Add field",
                data: {
                    [KEYCARDID]: cardId,
                    [KEYSUBMITACTIONID]: ACTIONSUBMITADDFIELD
                },
            },
            {
                type: "Action.Submit",
                title: "Submit",
            }
        ]
    };

    return MessageFactory.attachment(CardFactory.adaptiveCard(card));
}

Using this function, you can send the card initially like this in C#:

var inputCount = 1;
var cardId = Guid.NewGuid().ToString();
var reply = GenerateAdaptiveCardActivityWithInputs(inputCount, new Dictionary<string, string> { { KEYCARDID, cardId } });
var response = await turnContext.SendActivityAsync(reply, cancellationToken);
var dict = await InputCardStateAccessor.GetAsync(turnContext, () => new Dictionary<string, (string, int)>(), cancellationToken);

dict[cardId] = (response.Id, inputCount);

Node.js:

var inputCount = 1;
var cardId = Date.now().toString();
var reply = this.generateAdaptiveCardActivityWithInputs(inputCount, { [KEYCARDID]: cardId });
var response = await turnContext.sendActivity(reply);
var dict = await this.inputCardState.get(turnContext, {});
dict[cardId] = {
    activityId: response.id,
    inputCount: inputCount
};
await this.inputCardState.set(turnContext, dict);

And you can update the card in response to the card's "add field" submit action like this in C#:

private async Task AddFieldAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
    var activity = turnContext.Activity;

    if (activity.ChannelId == Channels.Msteams)
    {
        var value = JObject.FromObject(activity.Value);
        var cardId = Convert.ToString(value[KEYCARDID]);
        var dict = await InputCardStateAccessor.GetAsync(turnContext, () => new Dictionary<string, (string, int)>(), cancellationToken);

        if (dict.TryGetValue(cardId, out var cardInfo))
        {
            var update = GenerateAdaptiveCardActivityWithInputs(++cardInfo.InputCount, value);

            update.Id = cardInfo.ActivityId;
            update.Conversation = activity.Conversation;

            await turnContext.UpdateActivityAsync(update, cancellationToken);

            dict[cardId] = cardInfo;
        }
    }
}

Node.js:

async addField(turnContext) {
    var activity = turnContext.activity;

    if (activity.channelId == 'msteams') {
        var value = activity.value;
        var cardId = value[KEYCARDID];
        var dict = await this.inputCardState.get(turnContext, {});
        var cardInfo = dict[cardId];

        if (cardInfo) {
            var update = this.generateAdaptiveCardActivityWithInputs(++cardInfo.inputCount, value);

            update.id = cardInfo.activityId;
            update.conversation = activity.conversation;
            update.serviceUrl = activity.serviceUrl;

            dict[cardId] = cardInfo;

            await this.inputCardState.set(turnContext, dict);
            await turnContext.updateActivity(update);
        }
    }
}

Updated card

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66
  • Thanks, I tried the Action.ShowCard but I don't know how many times the user would like to add a new field, sometimes it can be a case where the user would like to add 100 fields so I wanted to know if there's any alternate method to avoid redundancy. It would be great if you could help me with the solution in node js. (P.S: I'm a beginner in adaptive cards) – Priya G Aug 13 '19 at 11:05
  • @PriyaG - Even if you're struggling to implement my solution you can still upvote the answer to show gratitude for the work I've done. If I show you how to implement the second option in Node.js will you accept my answer as well? If you're a beginner in Adaptive Cards then you might want to read my latest blog post: https://blog.botframework.com/2019/07/02/using-adaptive-cards-with-the-microsoft-bot-framework/ – Kyle Delaney Aug 13 '19 at 21:50
  • Thank you for putting up so much of effort, I do respect the efforts that you've put in to help me and I did up vote your post too, but since I'm a new member of StackOverflow too, I don't have 15+ reputation so my vote can't be displayed is what I got a message from StackOverflow Team :) Yes, I would accept your answer in Node js, because I've been struggling with it since a long time. – Priya G Aug 14 '19 at 06:29
  • @PriyaG - I have written code in Node.js for you. Please accept this answer. – Kyle Delaney Aug 22 '19 at 15:51
-3

yes this is possible you can look about the addRow in javascript

  • P.S. The adaptive card is designed in json, not javascript. So I want something like an onclick() function in json – Priya G Jul 24 '19 at 14:06