0

I would like to understand how to read/write data with Adaptive cards. I can read the data from a submit action, and reply as text, but not sure how present the input data in the card. First place, I would like to add the shootValue to an array that I can carry trough the lifecycle of the card. Can somebody please let me know how to do this?

The goal of this question is to understand how to keep existing responses from the card. Like in Battleship, I shoot "A1", type it in an input box, submit, I would like to see "A1" in the card. I add "A2", submit, then I would like to see "A1" and "A2" in the card that is sent to Teams. I understand that I need to rebuild the card from scratch at every shot, that means, I need to either carry on the shots somehow with each action.

Data card:

{
  "type": "AdaptiveCard",
  "version": "1.0",
  "body": [
    {
      "type": "TextBlock",
      "text": "Hello {name}"
    },
    {
      "type": "ColumnSet",
      "columns": [
        {
          "type": "Column",
          "width": "stretch",
          "id": "",
          "items": [
            {
              "type": "Container",
              "items": [
                {
                  "type": "Input.Text",
                  "placeholder": "Voorbeeld: A1",
                  "id": "id_shoot",
                  "$data": "shoot"
                }
              ]
            }
          ]
        },
        {
          "type": "Column",
          "width": "stretch",
          "items": [
            {
              "type": "Container",
              "items": [
                {
                  "type": "TextBlock",
                  "text": " {shoot}",
                  "horizontalAlignment": "Right",
                  "id": ""
                }
              ],
              "$data": "{shoots}",
              "id": "shotcoords"
            }
          ],
          "$data": "{shots}"
        },
        {
          "type": "Column",
          "width": "stretch",
          "items": [
            {
              "type": "Container",
              "items": [
                {
                  "type": "TextBlock",
                  "text": "{status}",
                  "id": ""
                }
              ],
              "$data": "{shoots}",
              "id": "shotstatuses"
            }
          ],
          "id": ""
        }
      ]
    },
    {
      "type": "ActionSet",
      "id": "",
      "actions": [
        {
          "type": "Action.Submit",
          "title": "Shoot",
          "id": "",
          "style": "positive",
          "data": {}
        }
      ]
    }
  ],
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json"
}

Data

{
  "name": "Test shot",
  "shoots": [
    {
      "shoot": "a1",
      "status": "hit"
    },
    {
      "shoot": "a2",
      "status": "hit"
    }
  ]
}
vilmarci
  • 451
  • 10
  • 31
  • It's unclear what you're asking. Would you please include your Adaptive Card along with some screenshots that explain what you're trying to do? – Kyle Delaney Dec 12 '19 at 02:34
  • Well, basically I need more an idea. I have a in input textbox and a submit action, I would like to present all results of the submits in the card. Example: I enter "1" in the input box, click submit, I would like to see "1" in the card. I enter "2", click submit, then I would like to see 1 and 2 in the card. – vilmarci Dec 12 '19 at 10:11
  • What channel are you using? – Kyle Delaney Dec 12 '19 at 18:06
  • I am using the Teams channel – vilmarci Dec 13 '19 at 07:56
  • Well Teams does support message updates. So you have a card with a text input and a submit action. You want to respond to the submit action by updating the card to include a new text block with the contents of the text input. Is that correct? Would you please edit your question to provide your Adaptive Card like I asked? – Kyle Delaney Dec 13 '19 at 22:21
  • Yes, I need to carry the previous inputs and show them in the card. Updated question with card and sample data. – vilmarci Dec 16 '19 at 07:42
  • You means display in Carousel ? – Eng Soon Cheah Dec 16 '19 at 08:36
  • Display in the adaptive card. – vilmarci Dec 16 '19 at 17:34
  • @vilmarci - Is my answer acceptable? – Kyle Delaney Dec 19 '19 at 23:23
  • @Kyle Sorry, was on holiday for a while. Thank you, but I still need to test your answer. I managed to discuss with the backend team to send most of the previous data, so I don't need to store them locally. – vilmarci Jan 09 '20 at 10:30

1 Answers1

0

There is no "simple" way to do this, but there is a way. The answer will be similar to this one.

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, List<string> Shots)>> BattleshipStateAccessor { get; internal set; }

Then you can instantiate it like this

BattleshipStateAccessor = _conversationState.CreateProperty<Dictionary<string, (string, List<string>)>>("battleshipState");

You have a few decisions to make here. First, I'm choosing to make the state property a dictionary so I can keep track of multiple cards and only update the specific card that was clicked. If you don't care about that then you don't need a dictionary and you don't need to worry about "card ID's," but saving at least one activity ID is necessary so that you'll be able to update the card. As for saving the "shots," you have a few choices here. You could save that state on the client side by updating the submit action's data with each shot that's been made, but I figure I might as well save the shots in bot state because I already need to save the activity ID in bot state anyway. Then there's the question of what information about each shot you should save. In this example I'm only saving the location of the shot that the user entered and not the status of the shot, since I figure I can always recalculate the status whenever I need to.

I've modified your submit action to look like this:

{
  "type": "Action.Submit",
  "title": "Shoot",
  "style": "positive",
  "data": {
    "behavior": "Shoot",
    "cardId": ""
  }
}

What I've done here is added two properties to your data object, and this data will be sent to your bot along with the text input's value. The "behavior" property will help your bot route to the correct function in case your bot uses multiple types of actions that can be handled different ways. The "cardId" property is just a placeholder that your bot code will fill in when creating the card. I've stored the names of these properties in the constants KEYBEHAVIOR and KEYCARDID.

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.

internal static IMessageActivity CreateBattleshipCardActivity(
    string cardId,
    object data = null)
{
    data = data ?? new
    {
        name = "Test shot",
        shoots = new string[0],
    };

    JObject card = CreateAdaptiveCard("battleship", data);

    foreach (var token in card.Descendants()
        .Select(token => token as JProperty)
        .Where(token => token?.Name == KEYCARDID))
    {
        token.Value = cardId;
    }

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

The CreateAdaptiveCard function loads the JSON template from a file with the given name, transforms it with the given data, and deserializes it into a JObject.

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

public async Task TestBattleshipAsync(
    ITurnContext turnContext,
    CancellationToken cancellationToken)
{
    var activity = turnContext.Activity;
    var cardId = Guid.NewGuid().ToString();
    var reply = CreateBattleshipCardActivity(cardId);
    var response = await turnContext.SendActivityAsync(reply, cancellationToken);
    var dict = await BattleshipStateAccessor.GetAsync(
        turnContext,
        () => new Dictionary<string, (string, List<string>)>(),
        cancellationToken);

    dict[cardId] = (response.Id, new List<string>());
}

And you can update the card in response to the card's "Shoot" submit action like this:

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

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

        if (dict.TryGetValue(cardId, out var savedInfo))
        {
            savedInfo.Shots.Add(value["id_shoot"].ToString());

            var data = new
            {
                name = "Test shot",
                shoots = savedInfo.Shots.Select(shot => new
                {
                    shoot = shot,
                    status = DetermineHit(shot),
                }),
            };

            var update = CreateBattleshipCardActivity(cardId, data);

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

            await turnContext.UpdateActivityAsync(update, cancellationToken);
        }
    }
}

Adaptive Card

Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66