Re-design actions to better support the reality of how actions are used
See original GitHub issueGOALS
- Make it possible for actions to be universal while still supporting custom actions
- Make supporting custom actions easy since it’s a very common scenario
- Rationalize actions and selectAction so that selectAction doesn’t have properties specific to action actions, like title
actions and selectAction rationalization Today, selectAction takes a type of Action. However, actions have properties like title (and in the future, image, etc). The title is meaningless to a selectAction, which by definition is just a touch target and therefore has no title to display.
Therefore, there needs to be a core invoke action that both of these share. Let’s call it Command. An Action has a title and a command, and a container just simply has a command property.
Proposal to rationalize selectAction Actions becomes one single type that contains a Command…
Property | Type | Required | Description |
---|---|---|---|
type | “Action” | false | |
title | string | true | Label for button or link that represents this action |
speak | Speak | false | Specifies what should be spoken for this entire element. This is simple text or SSML fragment |
command | IAdaptiveCommand | true | Command to be invoked when button is clicked |
selectAction on container/image/etc becomes command…
Property | Type | Required | Description |
---|---|---|---|
type | “Container” | true | |
items | CardElement[] | true | The items that are to be displayed in this container. |
actions | Action[] | false | Actions associated with this container |
command | IAdaptiveCommand | false | Command to be invoked when this container is clicked |
style | normal, emphasis | normal | a container can group elements together in a normal or emphasized style |
And then we introduce the IAdaptiveCommand type… The only properties are type and fallbackCommand. The inheriting command types get to define their properties…
Property | Type | Required | Description |
---|---|---|---|
type | string | true | The type of command. OpenUrl and ShowCard are natively supported. Hosts can have their own custom commands. |
fallbackCommand | IAdaptiveCommand | false | A command to be invoked if the specified command is not supported (more on this further down below) |
The two system-supported commands that will implement IAdaptiveCommand out of the box…
Property | Type | Required | Description |
---|---|---|---|
type | “OpenUrl” | true | An OpenUrl command. |
url | string | true | Default (browser) url to use |
Property | Type | Required | Description |
---|---|---|---|
type | “ShowCard” | true | A ShowCard command. |
card | AdaptiveCard | true | Card to be shown when this action is invoked. It is up to client to decide how to show this card (inline, popup, etc). |
WHAT A DEVELOPER SENDS
{
"type": "AdaptiveCard",
"body": [
{
"type": "Image",
"url": "https://unsplash.it/200",
"size": "auto",
"command": {
"type": "OpenUrl",
"url": "http://contoso.com/tomspie"
}
}
],
"actions": [
{
"type": "Action",
"title": "More Info",
"command": {
"type": "OpenUrl",
"url": "http://contoso.com/moreinfo"
}
}
]
}
RATIONALIZING UNIVERSAL AND CUSTOM ACTIONS Custom actions are a very common scenario that almost all hosts have. But we have a strong desire for these cards to be portable and work everywhere. Let’s look at how we can achieve both of these things…
WHAT WE HAVE TODAY
Toast notifications | OWA | Skype | ||
---|---|---|---|---|
Action.Http | NOT SUPPORTED | ? | NOT SUPPORTED | |
Action.OpenUrl | Need additional properties like PFN, maybe via extensions | Yes? | Yes, but handled by Skype app | |
Action.ShowCard | Yes | Yes? | Yes | |
Action.Submit | NOT SUPPORTED | Yes? | Yes, but uses custom data properties to determine submit types | |
Custom actions | Foreground activation Background activation Dismiss Snooze | Invoke Add-In command | Technically using Action.Submit as a custom action by adding custom properties in the data field |
Here’s some of my thoughts on the existing actions…
Action | Purpose | Proposed change |
---|---|---|
Action.Http | Not widely adopted, few hosts support it, therefore not universal. | Remove Action.Http. Hosts that want to support that can use a custom action. |
Action.OpenUrl | Universal action that can be supported by all hosts. Enables the same card to function correctly across multiple hosts. | Implement this by default in the renderers so universal and supported out-of-box (and allow extensibility if hosts want to handle it themselves) |
Action.ShowCard | Universal action that can be supported by all hosts. | Implement this by default in the renderers (using show inline) so it’s universal and supported out-of-box (and allow extensibility if hosts want to handle it themselves) |
Action.Submit | Hosts do their own thing, some provide special activations, etc | Make this extensible to reflect what hosts are actually doing, like “WindowsAction.Dismiss” or “SkypeAction.InlineReply”, etc. Action.Submit on its own simply isn’t portable and universal. |
WHY ACTION.SUBMIT ISN’T UNIVERSAL Action.Submit is, by definition, local to the host. On Skype, it’ll submit an action to your bot host - you need to have a connection to that bot wired up to submit that action. A Windows toast notification would have no way of submitting something to a Skype bot. So an Action.Submit written for a Skype bot would never just “magically work” on a toast notification.
PROPOSAL FOR UNIVERSAL ACTIONS AND BETTER CUSTOM ACTIONS Remove “Action.Submit” and allow any custom command. We’ll pass through all the properties back to the app. Devs can provide a fallbackCommand that can be universally supported.
"actions": [
{
"type": "Action",
"title": "Website",
"command": {
"type": "OpenUrl",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
}
},
{
"type": "Action",
"title": "Reviews",
"command": {
"type": "Toast.ForegroundActivation",
"arguments": "action=viewReviews&restaurant=TomsPie",
"fallbackCommand": {
"type": "OpenUrl",
"url": "https://yelp.com/TomsPie/reviews"
}
}
}
]
REQUIREMENTS
- Default universal commands are supported out of box (OpenUrl, ShowCard)
- Custom commands are able to be defined by hosts
- Custom commands support a fallbackCommand
- Action or touch target only rendered if it command be invoked (therefore host needs to support the custom command or there needs to be a universal fallback provided)
- fallbackCommand can be provided on a fallbackCommand, therefore allowing a series of fallbacks
Host provides their command handlers when configuring their renderer…
_renderer = new XamlCardRenderer();
_renderer.SetCommandHandler("Toast.BackgroundActivation", new ToastBackgroundActivationHandler());
_renderer.SetCommandHandler("Toast.ForegroundActivation", new ToastForegroundActivationHandler());
_renderer.SetCommandHandler("Toast.Snooze", new ToastSnoozeHandler());
_renderer.SetCommandHandler("Toast.Dismiss", new ToastDismissHandler());
private class ToastBackgroundActivationHandler : IAdaptiveCommandHandler
{
public void HandleCommand(IAdaptiveCommand command)
{
// TODO: Gather inputs and activate background task of app
}
}
When rendering, renderer checks whether handler is registered, if so, renders button… if not looks for fallback… if not drops button completely
private UIElement RenderAction(IAdaptiveActionElement action)
{
// If we have command handler
if (HasCommandHandler(action.Command.Type))
{
// Render button
return new Button() { Content = action.Title };
}
// Else if fallback provided
else if (action.Command.FallbackAction != null)
{
// Use the fallback command (note probably shouldn't actually modify object model)
action.Command = action.Command.FallbackAction;
// Try rendering with fallback
return RenderAction(action.FallbackAction);
}
// Otherwise drop the button
return null;
}
Then when the button is clicked…
private void InvokeCommand(IAdaptiveCommand command)
{
// Get the handler (default or custom)
IAdaptiveCommandHandler handler = GetCommandHandler(command.Type);
// And have it handle the command invokation
handler.HandleCommand(command);
}
Issue Analytics
- State:
- Created 6 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
Ah yes, that’s definitely a good point.
Personally, I am content with the current model and think that splitting things into command/action would be complicating things unnecessarily for card authors.
That said, I agree with your arguments, and fundamentally you are probably right. If there is a consensus around doing it, I’m ok with it.
Whoops, what I meant to say is that tooltips aren’t exclusively on clickable things. Therefore if we added them, we would add them to the generic inline AdaptiveElement rather than adding it to actions. That way devs could use them on both interactive and non-interactive elements.