0

I am using adaptive card templating in bot framework. When user selects a particular value from a dropdown, based on the selection, few input fields on the input form card should be auto populated. How to achieve this?

Based on the dropdown, if user chooses 'myself', his email id should be auto populated in his/her email address textbox(email address I can get from user profile stored in user state). The adaptive card I am using is as below:

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "Please enter the email Id, on behalf of whom you want to raise the request.",
            "wrap": true
        },
        {
            "type": "Input.ChoiceSet",
            "id":"dropdown",
            "choices": [
                {
                    "title": "Myself",
                    "value": "Myself"
                },
                 {
                    "title": "Other",
                    "value": "Other"
                }
            ],
            "placeholder": "Raise request for"
        },
        {
            "type": "Input.Text",
            "id": "email",
            "placeholder": "Enter email address here",
            "validation": {
                "necessity": "Required",
                "errorMessage": "Email address is required"
            }
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.Submit",
                    "title": "Submit",
                    "data": "Submit"
                }
            ]
        },
        {
            "type": "ActionSet",
            "actions": [
                {
                    "type": "Action.Submit",
                    "title": "Cancel",
                    "data": {
                        "id": "stepCancel"
                    }
                }
            ]
        }
    ]
}

I used actions instead of actionset. The card looks something like this:

{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.2",
    "body": [
        {
            "type": "TextBlock",
            "text": "Please enter the email Id, on behalf of whom you want to raise the request.",
            "wrap": true
        },
        {
            "type": "Input.ChoiceSet",
            "id": "dropdown",
            "choices": [
                {
                    "title": "Myself",
                    "value": "Myself"
                },
                {
                    "title": "Other",
                    "value": "Other"
                }
            ],
            "placeholder": "Raise request for"
        },
        {
            "type": "Input.Text",
            "id": "email",
            "placeholder": "Enter email address here",
            "validation": {
                "necessity": "Required",
                "errorMessage": "Email address is required"
            },
            "inlineAction": {
                "type": "Action.Submit",
                "title": "Submit"
            }
        }
    ]
}
quest
  • 87
  • 9
  • What have you tried so far? – Kyle Delaney Oct 19 '20 at 19:35
  • Currently, I created two adaptive cards based on each dropdown selection and handling the display of Cards based on if statement in code. I am trying to see if this is possible client side. – quest Oct 20 '20 at 18:39
  • Can you show us your code and your Adaptive Cards? What channel are you using? This answer should help you come up with a client-side solution if you're using Web Chat: https://stackoverflow.com/questions/60877533/botframework-webchat-adaptive-card/61621137#61621137 – Kyle Delaney Oct 20 '20 at 21:46
  • Are you still working on this? – Kyle Delaney Oct 23 '20 at 21:41
  • I found a feature which I think might be useful for this use case i.e the use of 'Conditional layout with $when' as mentioned in this link https://learn.microsoft.com/en-us/adaptive-cards/templating/language – quest Oct 27 '20 at 09:18
  • The conditions in the Adaptive Card templating language are evaluated before the card ever gets rendered and so they can't be used to respond to user input. Templating is useful but it's used for authoring and not rendering. What you're looking for is Adaptive Card [extensibility](https://learn.microsoft.com/en-us/adaptive-cards/rendering-cards/extensibility). If you'd like some help, would you mind answering my previous questions? – Kyle Delaney Oct 27 '20 at 22:22
  • Thanks for the response Kyle. Yes, I am still working on this. The auto populate feature is required at many places in my conversational flow, since the use case is a lot data driven. – quest Oct 28 '20 at 06:52
  • Added the card as requested. I am using direct line web chat. And language is c#, not node js – quest Oct 28 '20 at 06:59
  • Is there a reason you're using action sets instead of the `actions` property of the Adaptive Card itself? – Kyle Delaney Oct 30 '20 at 22:35
  • I think I can help you build a solution but I need to know if you're still working on this – Kyle Delaney Nov 03 '20 at 00:22
  • Thanks Kyle for you reply. I am relatively new to this and didn't realise that actions also exists. I did try actions, few days back and have updated the question with the card. Yes i am still working on this and there are many places in my code where i need this dynamic behaviour like autopopulate or autohide etc. – quest Nov 03 '20 at 07:36
  • @KyleDelaney Another approach I tried was using Graph API, when user selects myself in the dropdown, in the backend code I have a condition like if (buttonclicked =="Myself"), then call the graph API and get the email ID. Since autopopulate was not happennig, I handled it in backend. But again is it possible to even hide the 'email address' textbox if dropdown value is 'Myself' ? Any help would be greatly appreciated. – quest Nov 03 '20 at 07:40
  • Hiding the text input would be much easier than auto-populating it, but that's a different question. Would you accept an answer that hides the text input? And when you said you didn't know that actions also exists, why did you then update your card to include an inline action instead of the actions property? If the action is part of the text input and you hide the text input then the inline action will also be hidden and that's not what you want. Have you read this blog post? https://blog.botframework.com/2019/07/02/using-adaptive-cards-with-the-microsoft-bot-framework/ – Kyle Delaney Nov 03 '20 at 19:00
  • I have read the blog before and that definitely is the only and best material i could find online to understand and implement adaptive cards input forms. Thank you for that. Also, I assumed actions can only exist inside an actionSet, that is the reason why i designed it likewise. But after revisiting your blog I understand actions can exist independently withour an actionSet. And inline action is something I came to know about recently and got confused with actions. now things are sorted out. Thanks to your blog again. – quest Nov 04 '20 at 06:26
  • Lets figure out a solution with auto-populate. Auto-hide, if required I will post a different question altogether. – quest Nov 04 '20 at 06:49
  • Is my answer acceptable? – Kyle Delaney Nov 26 '20 at 19:44

1 Answers1

0

If you want to get Adaptive Cards to do something they were not designed to do, you will need to write your own renderer code using Adaptive Cards extensibility. You can use this answer and its many linked answers as your guide.

Linked questions

I've found that the usual pattern for these kinds of issues has two parts: come up with your own "schema" for your code to read, and then write the code to read it. If you want a choice set input to populate a certain text input with a certain value when a certain choice is selected, then your schema has three pieces of information: the text input's ID, the value to populate it with, and the choice that triggers it. Since this is a lot of information, it would be ideal if you could have all three of those pieces in their own "populate" object property like this:

{
    "type": "Input.ChoiceSet",
    "id": "dropdown",
    "choices": [
        {
            "title": "Myself",
            "value": "Myself"
        },
        {
            "title": "Other",
            "value": "Other"
        }
    ],
    "placeholder": "Raise request for",
    "populate": {
        "target": "email",
        "with": "my_email@email.com",
        "when": "Myself",
    },
},

Unfortunately, Direct Line will strip out additional properties from your Adaptive Card so they'll never reach Web Chat. There is a workaround for that problem where you can preserve your full JSON by tricking Direct Line into thinking it's not an Adaptive Card and then making sure Web Chat knows it's an Adaptive Card when it receives it.

If you don't use the workaround, you will need to find some way of getting those three pieces of information to Web Chat. If don't want your code to be reusable for different cards then you can just hardcode the name of the text input and the choice into your JavaScript so that it doesn't need to be transmitted through the card. If you go the usual route of putting the information in the id property then it could look pretty crowded, and a dynamic ID could be hard for your bot to read after the data is submitted:

"id": "populate_email_when_Myself_with_my_email@email.com",

If the user's email is already stored on the Web Chat side somehow then you won't need to transmit it through the card, and the ID could just be "populate_email_when_Myself". That's a bit less imposing, and it would be easier for your bot to find because it's not dynamically generated. I'll go even further by assuming your client has the email address already and the text input's ID "email" is hardcoded in the JavaScript, so the choice set input ID can just be "populateEmail_Myself".

Once you're ready to write the JavaScript code, I usually like to go with onParseElement so most of my code only gets executed at the beginning as part of a sort of initialization. However, I've discovered that a problem with onParseElement is that you'll only have access to the elements that have already been parsed, so if your text input is parsed after your choice set input then the onParseElement function for the choice set input won't have access to the text input. You could solve that problem by having the function look for an element at the very end of your card so that the function will have access to all the other elements, but my example here just uses onInputValueChanged instead of onParseElement. Note that this means the function will be executed any time any input value is changed in any card, including each time a key is pressed in a text input.

window.AdaptiveCards.AdaptiveCard.onInputValueChanged = input => {
  const PREFIX_POPULATE_EMAIL = 'populateEmail_';

  if (input.id && input.id.startsWith(PREFIX_POPULATE_EMAIL)) {
    // Myself
    const targetValue = input.id.slice(PREFIX_POPULATE_EMAIL.length);
    // The Adaptive Card object
    const card = input.getRootElement();
    // The text input with the hardcoded ID
    const emailElement = card.getElementById('email');

    // Did the user choose "Myself"?
    if (input.value == targetValue) {
      emailElement.renderedInputControlElement.value = THEIR_EMAIL_ADDRESS;
    }
  }
}

In many cases you'll need to re-render the element after you modify it by calling emailElement.render(). We don't need to do that in this case because we're changing the value in the rendered element directly with renderedInputControlElement rather than changing the value in the internal unrendered card element. This is a bit hacky because renderedInputControlElement is marked as protected in the TypeScript file, but if you wanted to do things the more official way then I suspect you'd have to rerender the whole card.

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