How to make searchable drop-down in adaptive cards - botframework

I have a message extension in which one of the Action command shows a form which is basically an adaptive card. The card contains a drop-down.
Here I am interested is there any way to perform the search on the list.
See the image and code below for more clearance.
I am developing in Node.js
const card = CardFactory.adaptiveCard({
version: '1.0.0',
type: 'AdaptiveCard',
body: [
{
"type": "TextBlock",
"text": "Route Name",
"size": "Medium",
"weight": "Bolder",
"spacing": "Large"
},
{
"type": "Input.ChoiceSet",
id: "routeId",
"spacing": "small",
"choices": routedDd,
"placeholder": "Select an option",
"value": "Select an option"
}
],
actions: [
{
type: 'Action.Submit',
title: 'Submit'
}
]
});
return {
task: {
type: 'continue',
value: {
card: card,
heigth: 200,
width: 400,
title: 'Add Task'
}
}
};

I think this feature hasn't yet been released, but it's on the roadmap. See https://www.youtube.com/watch?v=SEsjrFIf110

Related

How to get Microsoft Teams card to display correctly

I'm building a Microsoft Teams app with the botbuilder in typescript. Everything is working fine except the UI is not matching exactly what I want. Im using an adaptive card to render my responses to the user and typically the adaptive card has a top border (seen below is black) but mine doesnt have it and i'm trying to figure out how i can make it work.
I want my card to have the black border on top as shown here for in github's bot:
See image
This is what mine currently shows:
see image
below is my code to render the card. What i'm i doing wrong?
await context.sendActivity({ attachments: [
CardFactory.adaptiveCard({
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0",
"msteams": {
"width": "Full"
},
"body": [
{
"type": "TextBlock",
"text": "My card doesnt have the border.",
"wrap": true
}
]
})
] });
I figured it out and it was a simple fix. The code now looks like this.
"body": [
{
"type": "TextBlock",
"text": "My card doesnt have the border.",
"wrap": true,
"style": {
"borderTopWidth": 1,
"borderTopColor": "#000"
}
}
] });
I finally figured out the solution. I had to the office 365 connector card to get that color strip. FINALLY!
Here's the code sample for Nodejs/typescript:
CardFactory.o365ConnectorCard({
"title": "card title",
"text": "card text",
"summary": "O365 card summary",
"themeColor": "#E67A9E",
"sections": [
{
"text": "This is some <strong>bold</strong> text"
},
{
"text": "This is some <em>italic</em> text"
},
{
"text": "This is some <strike>strikethrough</strike> text"
},
{
"text": "<h1>Header 1</h1>\r<h2>Header 2</h2>\r <h3>Header 3</h3>"
},
{
"text": "bullet list <ul><li>text</li><li>text</li></ul>"
},
{
"text": "ordered list <ol><li>text</li><li>text</li></ol>"
},
{
"text": "hyperlink Bing"
},
{
"text": "embedded image <img src=\"https://aka.ms/Fo983c\" alt=\"Duck on a rock\"></img>"
},
{
"text": "preformatted text <pre>text</pre>"
},
{
"text": "Paragraphs <p>Line a</p><p>Line b</p>"
},
{
"text": "<blockquote>Blockquote text</blockquote>"
}
]
})

Dynamic ChoiceSet in Adaptive Cards "Post an Adaptive Card to a Teams user and wait for a response"

I have the following JSON code for an Adaptive Card. I put this into a "Compose" Data operation, and then used the "Post your own adaptive card as the Flow bot to a user" and it successfully works. However, using this REST API doesn't work when you want to wait for a response. So I tried to use "Post an Adaptive Card to a Teams user and wait for a response", but when I try to use the following JSON code below, it is giving me an error of... "[Validation] An Input.ChoiceSet must have at least one choice defined."
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.2",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "Input.Text elements",
"horizontalAlignment": "Center",
"wrap": true
},
{
"type": "TextBlock",
"text": "Part #",
"wrap": true
},
{
"type": "Input.ChoiceSet",
"id": "choiceSelect",
"choices": #{variables('ArrayTest')}
},
{
"type": "TextBlock",
"text": "Due Date",
"wrap": true
},
{
"type": "Input.Date",
"id": "DateVal"
},
{
"type": "TextBlock",
"text": "Start time",
"wrap": true
},
{
"type": "Input.Time",
"id": "TimeVal"
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit"
}
]
}
So after receiving this error, I tried to manipulate the code and I ended up changing the ChoiceSet to this
{
"type": "Input.ChoiceSet",
"id": "choiceSelect",
"choices": [
{
"title": "#{variables('ArrayTest')}",
"value": "#{variables('ArrayTest')}"
}
]
}
And I did not receive in a error in the Adaptive Card, but rather a Power Automate error of...
"The request failed. Error code: 'InvalidJsonInBotAdaptiveCard'. Error Message: 'Microsoft.Azure.ProcessSimple.Data.Entities.Exceptions.ProcessSimpleDataException: The specified Teams flowbot adaptive card request is missing or invalid. The tracking Id is '{0}'. ---> Newtonsoft.Json.JsonReaderException: After parsing a value an unexpected character was encountered: t. Path 'body[2].choices[0].title', line 1, position 356."
My question is... How can I use a dynamic ChoiceSet while also utilizing the "wait for a response"? I successfully can get it in a "Post your own adaptive card as the Flow bot to a user" but not a "Post an Adaptive Card to a Teams user and wait for a response"

How to create joi schema to validate object of objects

I tried to make schema to validate json such this:
{
"integration": { "module": [ "m" ] },
"tile": {
"title": "TTT",
"text": "ttt",
"icon": "./resources/main-icon.png",
"tags": [ "bbb", "vvvv"],
"orderNumber": 20
},
"steps": {
"order": [
"1",
"2",
"3"
],
"data": {
"1": {
"title": "tt1",
"description": "",
"screens": { "default": "true" }
},
"2": {
"title": "tt2",
"description": "",
"screens": { "default": "true" }
},
"3": {
"title": "tt3",
"description": "",
"screens": { "default": "true" }
}
}
}
};
Schema:
Joi.object({
integration: Joi.object({
module: Joi.array().items(Joi.string().valid('m').required())
}).required(),
tile: Joi.object({
title: Joi.string().required(),
text: Joi.string().required(),
icon: Joi.string().required(),
tags: Joi.array().items(Joi.string()).required(),
orderNumber: Joi.number().integer().min(1).max(255).required()
}).required(),
steps: Joi.object({
order: Joi.array().items(Joi.string()).required(),
data: Joi.object().keys({
title: Joi.string().required(),
description: Joi.string().required(),
screens: Joi.object({
default: Joi.string().valid('true', 'false').required()
}).required()
}).unknown(),
}).required()
});
But it generate error:
Validation Error: "steps.data.title" is required. "steps.data.description" is required. "steps.data.screens" is required
Please help. How can I make this schema?
Your data key is an object with keys 1, 2, and 3, each one is also an object with keys title, description, and screens.
But in your validation, your data key is an object with keys title, description, and screens, which is not correct.
You should change your steps.data validation to this:
data: Joi.object().pattern(
Joi.string().valid("1", "2", "3"),
Joi.object().keys({
title: Joi.string().required(),
description: Joi.string().required().allow(''),
screens: Joi.object({ default: Joi.string().valid('true', 'false').required() }),
})).unknown(),
}).required()
I used Joi.object().pattern to avoid duplicating the code since your object value is the same for each key.
I also changed your data.description, since you were not allowing empty strings, I just added .allow('').

Dependent field in Laravel Nova using Nova Dependency Container

I'm quite new to Laravel Nova, and I'm trying to implement dependent fields but there's no easy solution to it. I want to display a checkbox if a certain value is selected in a select dropdown.
After a little searching, I found this: https://github.com/epartment/nova-dependency-container. I'm trying to use this, but something's not working right. I'm using Laravel v5.7 and Nova v1.3.2. Hence, I've installed v1.1.2 of this package.
I followed the installation guidelines and have written the following code:
<?php
namespace App\Nova;
...
use Epartment\NovaDependencyContainer\HasDependencies;
use Epartment\NovaDependencyContainer\NovaDependencyContainer;
...
class Submission extends Resource
{
use HasDependencies;
....
public function fields(Request $request)
{
$fields[] = Select::make('Route', 'route')->options([
0 => 'First Name',
1 => 'First Name / Last Name',
2 => 'Full Name'
])->displayUsingLabels();
$fields[] = NovaDependencyContainer::make([
Text::make('Title', 'title')
])->dependsOn('route', 2);
...
return $fields;
}
....
}
?>
I can see the field Route with the right options and values. However, when I select 'Full Name', the dependent field Title is not displayed.
When I see the JSON response of the fields, this is what I get:
...
{
"component": "select-field",
"prefixComponent": true,
"indexName": "Route",
"name": "Route",
"attribute": "route",
"value": null,
"panel": null,
"sortable": false,
"textAlign": "left",
"options": [
{
"label": "First Name",
"value": 0
},
{
"label": "First Name / Last Name",
"value": 1
},
{
"label": "Full Name",
"value": 2
}
]
},
{
"component": "nova-dependency-container",
"prefixComponent": true,
"indexName": "",
"name": "",
"attribute": "",
"value": [],
"panel": null,
"sortable": false,
"textAlign": "left",
"fields": [
{
"component": "text-field",
"prefixComponent": true,
"indexName": "Title",
"name": "Title",
"attribute": "title",
"value": null,
"panel": null,
"sortable": false,
"textAlign": "left"
}
],
"dependencies": [
{
"field": "route",
"value": 2
}
]
},
...
I'm not sure where I'm going wrong. Would be great if someone could guide me here. Or if there's another solution that works.
Thanks!

How to handle tap action on adaptive card bot framework v4?

I am having an adaptive card which will be displayed in waterfall dialog. I want to know is it possible to capture the tap action on the adaptive card I don't want to add button to handle click. I am using v4 version of bot framework.
Tap dialog is triggered twice
My adaptive card :
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "0.5"
};
This is how i have updated the code
Code
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(PUBLICTRANS, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
async promptForTap(step) {
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
};
const reply = {
attachments: [CardFactory.adaptiveCard(Card1)]
};
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected `);
// Send Back Channel Event
await step.context.sendActivity({ type: 'event', name: 'tapEvent' });
return await step.endDialog();
}
output the card is triggered twice
You can send a Back Channel Event to WebChat from the Bot when the user clicks on an AdaptiveCard to trigger an event on the web page.
Simple AdaptiveCard with Select Action
First, create an AdaptiveCard that has a selectAction. Note, you can either add a selectAction to the whole card or different components in the card like an image or a column. When the user clicks on a part of the card that has a selectAction, it will send an activity to the Bot that contains the data attribute from the action that we can use to determine which action was triggered on the bot side.
Be sure to set the title attribute, the data field, and the type which should be Action.Submit.
{
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": { "tap": "Play again"}
},
"body": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"size": "Medium",
"weight": "Bolder",
"text": "Click Me!"
},
{
"type": "Image",
"url": "https://usercontent2.hubstatic.com/13896379.jpg"
}],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
Bot Code - Node Js
Constructor
const TAP_PROMPT = 'tap_prompt';
constructor(conversationState, userState) {
...
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(WHO_ARE_YOU, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
}
AdaptiveCard Prompt
In one of the steps of your Waterfall, you can create a reply object that has the AdaptiveCard as an attachment. Then you can pass that reply to the prompt. I would recommend using a TextPrompt for this step.
// This step in the dialog sends the user an adaptive card with a selectAction
async promptForTap(step) {
const reply = {
attachments: [CardFactory.adaptiveCard(Card)]
}
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
Capture AdaptiveCard selecteAction
In the next step of the Waterfall, you can get the data attribute sent from the AdaptiveCard by accessing step.value. Here, send an activity to the bot with a type and a name property. These two attributes will be used to filter incoming activities in WebChat and trigger the proper event.
// This step captures the tap and sends a back channel event to WebChat
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected ${step.context.activity.value.tap}`);
// Send Back Channel Event
await step.context.sendActivity({type: 'event', name: 'tapEvent'});
return await step.endDialog();
}
WebChat Middleware
In WebChat, we are going to create a custom middleware to check incoming activities. When we encounter an activity that has a name and type that we recognize, trigger your event on the webpage. In the example below, I just alerted the use that they clicked on the AdaptiveCard.
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { name, type } = action.payload.activity;
if (type === 'event' && name === 'tapEvent') {
alert("You tapped on the AdaptiveCard.");
}
}
return next(action);
}
);
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
For more details on back channel events and creating a custom middleware in WebChat, checkout this sample in the WebChat Repo.
Hope this helps!

Resources