Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃 [Preview] Templating language, a.k.a. Adaptive Cards "2.0" #2448

Open
matthidinger opened this issue Feb 22, 2019 · 12 comments

Comments

@matthidinger
Copy link
Member

commented Feb 22, 2019

We're excited to announce an early preview of our templating language that will be used in Adaptive Cards "2.0". This is very much "still baking" and subject to change, so your feedback is not only welcome, but is critical to ensure we deliver the features you need.

The templating language is a separate library, and can be used today with Adaptive 1.1 to send cards!!

Update 10/8/2019

Please see the official documentation for the latest

https://docs.microsoft.com/en-us/adaptive-cards/templating/

Original issue text

This is the original issue text, which has been moved to the official documentation above.

Introduction

Templating enables the separation of data from view in your Adaptive Card.

The templating library combines your data and view template and produces a final Adaptive Card.

The data can be provided inline with the AdaptiveCard payload, or at runtime using new APIs.

Specify data within the card

To specify data directly within the card payload, simply add a $data attribute to your AdaptiveCard (seen below).

Binding to the data

Then you can bind to the data within the body or actions of the card.

  • Binding syntax starts with { and ends with }. E.g., {myProperty}
  • Dot-notation to access sub-objects
  • Indexer syntax to retrieve properties by key or items in an array
  • Graceful null handling for deep hierarchies
  • Escape syntax documentation to come soon
{
    "type": "AdaptiveCard",
    "$data": {
        "employee": {
            "name": "Matt",
            "manager": { "name": "Thomas" },
            "peers": [{
                "name": "Andrew" 
            }, { 
                "name": "Lei"
            }, { 
                "name": "Mary Anne"
            }, { 
                "name": "Adam"
            }]
        }
    },
    "body": [
        {
            "type": "TextBlock",
            "text": "Hi {employee.name}! Here's a bit about your org..."
        },
        {
            "type": "TextBlock",
            "text": "Your manager is: {employee.manager.name}"
        },
        {
            "type": "TextBlock",
            "text": "3 of your peers are: {employee.peers[0].name}, {employee.peers[1].name}, {employee.peers[2].name}"
        }
    ]
}

Separating the template from the data

Alternatively, you could create a re-usable card "template" without the data:

EmployeeCard.json

{
    "type": "AdaptivCard",
    "body": [
        {
            "type": "TextBlock",
            "text": "Hi {employee.name}! Here's a bit about your org..."
        },
        {
            "type": "TextBlock",
            "text": "Your manager is: {employee.manager.name}"
        },
        {
            "type": "TextBlock",
            "text": "3 of your peers are: {employee.peers[0].name}, {employee.peers[1].name}, {employee.peers[2].name}"
        }
    ]
}

And provide the data to the template at runtime:

C# example

var myEmployee = new Employee() 
{ 
    Name = "Matt",
    Manager = "Thomas",
    Peers = new List<Employee>() 
    { 
        new Employee() { Name = "Andrew" },
        new Employee() { Name = "Lei" },
        new Employee() { Name = "Mary Anne" },
        new Employee() { Name = "Adam" },
    }
};

var card = AdaptiveCard.FromJson("EmployeeCard.json", myEmployee);

Designer Support

We are pleased to announce a preview of the designer as well.

Try it out now: http://vnext.adaptivecards.io/designer

image

This "vnext" URL is going to have bugs and will deploy frequently. Clear your cache daily to make sure you have the latest, and if you find bugs please let us know!

  • Sample Data Editor - Specify sample data here to view the data-bound card when in "Preview Mode." There is a small button in this pane to populate the Data Structure from the existing sample data.
  • Data Structure - This is the structure of your sample data. Fields can be dragged onto the design surface to create a binding to them
  • Preview Mode - Press the toolbar button to toggle between the edit-experience and the sample-data-preview experience
  • Open Sample - click this button to open various sample payloads

Advanced binding

Binding scopes

There are a few reserved keywords to access various binding scopes.

Note: not all of these are implemented in the preview.

{
    "{<property>}": "Implicitly binds to `$data.<property>`",
    "$data": "The current data object",
    "$root": "The root data object",
    "$index": "The current index when iterating",
    "$host": "Access properties of the host *(not working yet)*"
}

Assigning a data context to elements

To assign a data context to any element add a $data attribute to the element.

{
    "type": "Container",
    "$data": "{mySubObject}",
    "items": [
        {
            "type": "TextBlock",
            "text": "This TextBlock is now scoped directly to 'mySubObject': {mySubObjectProperty}"
        },
        {
            "type": "TextBlock",
            "text": "To break-out and access the root data, use: {$root}"
        }
    ]
}

Repeating items in an array

This part is a bit of "dark magic".

  • If an element's $data context is assigned to an array, then the element will be repeated for each item in the array.
  • As it is being repeated, $data becomes scoped to the individual item within the array.

For example, the TextBlock below will be repeated 3 times since it's $data is an array. Notice how the text property is bound to the name property of an individual object within the array.

{
    "type": "Container",
    "items": [
        {
            "type": "TextBlock",
            "$data": [
                { "name": "Matt" }, 
                { "name": "David" }, 
                { "name": "Thomas" }
            ],
            "text": "{name}"
        }
    ]
}

Resulting in:

{
    "type": "Container",
    "items": [ 
        {
            "type": "TextBlock",
            "text": "Matt"
        },
        {
            "type": "TextBlock",
            "text": "David"
        }
        {
            "type": "TextBlock",
            "text": "Thomas"
        }
    ]
}

Functions

We will provide a set of functions that work on every SDK.

The syntax here is still up in the air so please check back soon, but here's a start of what we're planning:

String functions

  • substr
  • indexOf (not working yet)
  • toUpper (not working yet)
  • toLower (not working yet)

Number functions

  • Formatting (currency, decimal, etc) (not working yet)

Date functions

  • Parsing well-known date string formats (not working yet)
  • Formatting for well-known date/time representations (not working yet)

Data manipulation

  • JSON.parse - ability to parse a JSON string

JSON.parse Example

This is an Azure DevOps response where the message property is a JSON-serialized string. In order to access values within the string, we need to use the JSON.parse function in our template.

Data

{
    "id": "1291525457129548",
    "status": 4,
    "author": "Matt Hidinger",
    "message": "{\"type\":\"Deployment\",\"buildId\":\"9542982\",\"releaseId\":\"129\",\"buildNumber\":\"20180504.3\",\"releaseName\":\"Release-104\",\"repoProvider\":\"GitHub\"}",
    "start_time": "2018-05-04T18:05:33.3087147Z",
    "end_time": "2018-05-04T18:05:33.3087147Z"
}

Usage

{
    "type": "TextBlock",
    "text": "{JSON.parse(message).releaseName}"
}

Resulting In

{
    "type": "TextBlock",
    "text": "Release-104"
}

Custom functions

We want to make sure Hosts can add custom functions, which means we need robust support for fallback support if a function isn't supported. We are still evaluating this.

Conditional layout

We currently expose a $when clause allowing a template to toggle the visibility of an element based on the evaluation of a binding.

{
    "type": "AdaptiveCard",
    "$data": {
        "price": "35"
    },
    "body": [
        {
            "type": "TextBlock",
            "$when": "{price > 30}",
            "text": "This thing is pricy!",
            "color": "attention",
        },
         {
            "type": "TextBlock",
            "$when": "{price <= 30}",
            "text": "Dang, this thing is cheap!",
            "color": "good"
        }
    ]
}

SDK support

Currently you need to build the SDKs from source in order to use data binding outside of the designer. 馃槶 We are working on a plan to release preview packages for .NET and JavaScript to make experimenting with these previews easier on everyone.

.NET

  • Clone the AdaptiveCards repo
  • git checkout mahiding/communitycall-2-19
  • Open the Visual Studio Solution at source/dotnet/AdaptiveCards.sln
  • Make a new project that references the AdaptiveCards project
  • Use new FromJson overloads to pass a template and data separately: AdaptiveCards.FromJson(myTemplate, myData)

AdaptiveCardsStockBot Sample

image

Or check out the Stock Bot sample that includes 2 templates that get populated from a real Stock Quote API.

  • Install the Bot Framework emulator
  • Open the Visual Studio Solution at source/dotnet/AdaptiveCards.sln
  • Set the Startup Project to Samples/AdaptiveCardsStockBot
  • Start the Bot Framework Emulator and F5, you should be able to connect to localhost and talk to the bot

JavaScript

  • Clone the AdaptiveCards repo
  • git checkout mahiding/communitycall-2-19
  • The template engine exists in source/nodejs/adaptivecards-designer/src/template-engine

What's next and sending feedback

As you can imagine, data binding and the separation of card templates from data enables a slew of possibilities toward our mission of "an ecosystem of exchangeable card content in a common and consistent way".

We're eager to share more as soon as we can. In the meantime please give feedback here or Twitter @MattHidinger/#AdaptiveCards.

Examples

We only have a limited amount of samples created so far, but take a look here to get started.

@matthidinger matthidinger pinned this issue Feb 22, 2019
@matthidinger matthidinger changed the title [Preview] Data Binding in Adaptive Cards "2.0" 馃 [Preview] Data Binding in Adaptive Cards "2.0" Feb 22, 2019
@v-kydela

This comment has been minimized.

Copy link
Contributor

commented Feb 22, 2019

@matthidinger - This is all very excellent! I'm on the Bot Framework support team and I use Adaptive Cards frequently and I'm always excited to hear new things about Adaptive Cards.

Since you asked, I do have some feedback. It seems to me like there are two ways to implement these features and I'm not sure what path you're taking.

  1. This could be a new version of the Adaptive Cards schema which would mean all this new syntax would be included in the payload being sent to whatever channel is meant to render the card.
  2. This could just be a new version of the Adaptive Cards packages, meaning a card would be converted from this new syntax into the old syntax before being sent to the channel.

Since this is being called Adaptive Cards "2.0" I'm a little confused. The name 2.0 implies that it's a new schema like Adaptive Cards 1.1. But it's in quotes, which might imply that you don't mean that.

It seems to me like this whole data binding feature could very easily be implemented using option 2, and my feedback is that it should be. That's not to say I don't think there should be any new versions of the schema. It's just that from my point of view, if you can implement a feature without requiring a new schema then it's very logical to do so.

Channels have been slow to implement support of Adaptive Cards 1.1, so we might expect the same regarding Adaptive Cards 2.0. If we can implement this new feature such that we're not counting on channels to implement support of a new Adaptive Cards schema, we'll get all the benefits of the new feature while still being able to reach as many channels as possible. The way to do this is of course to have cards containing the data binding syntax get converted into "classic" Adaptive Cards syntax on the bot side, before the card is sent to the channel. That way the channel wouldn't need to be responsible for interpreting the syntax.

@andrewleader

This comment has been minimized.

Copy link
Collaborator

commented Feb 22, 2019

@v-kydela that definitely could be possible! From the Bot Framework side, you could parse the new template/data card using the new Adaptive Card library, and then call a "populate" method to populate the data into the template, generating a fully self-contained Adaptive Card, which you then can send to all the channels.

Our current thoughts is that the templating/data binding will happen BEFORE rendering, so you can apply the templating ahead of time. Authors could even choose to apply the templating before they even send Bot Framework the card.

Good to know that you might use this feature in that way! Thanks!

@FranckyC FranckyC referenced this issue May 24, 2019
1 of 1 task complete
@andrewleader andrewleader changed the title 馃 [Preview] Data Binding in Adaptive Cards "2.0" 馃 [Preview] Templating language, a.k.a. Adaptive Cards "2.0" May 27, 2019
@AndreMantas

This comment has been minimized.

Copy link

commented Jul 16, 2019

When will we be able to install a pre-release via nugget? We are waiting for this feature for so long :)

@andrewleader

This comment has been minimized.

Copy link
Collaborator

commented Jul 16, 2019

Hey @AndreMantas, you're looking for a .NET version? We have a JavaScript version on NPM: https://www.npmjs.com/package/adaptivecards-templating

Let us know if you want the .NET version on NuGet and we probably could get around to packaging up the current alpha! Also let us know what version of .NET you need it on (.NET standard 1.3, 2.0, or .NET PCL, etc)

@AndreMantas

This comment has been minimized.

Copy link

commented Jul 16, 2019

Hi. Yes I'm looking for a .NET Core version.
Currently when creating new bots via the microsoft template I believe projects are set to .NET Core 2.1, but I usually switch to 2.2.

@andrewleader

This comment has been minimized.

Copy link
Collaborator

commented Jul 16, 2019

@AndreMantas we've got a PR in-progress (#3239) for a .NET Standard 2.0 library, you could compile from that source, or here's a pre-compiled NuGet from OneDrive that you could copy locally and consume until we get it merged and published to NuGet.org!

See the readme for a quick explanation on how to use the .NET library!

@rvinothrajendran

This comment has been minimized.

Copy link

commented Sep 23, 2019

@matthidinger,@andrewleader where I can download the new AdaptiveCard.FromJson API supporting package for C#

@matthidinger

This comment has been minimized.

Copy link
Member Author

commented Sep 25, 2019

@paulcam206 is working on this as we speak, hopefully not much longer

@matthidinger

This comment has been minimized.

Copy link
Member Author

commented Oct 8, 2019

@rvinothrajendran please see the package here: https://www.nuget.org/packages/AdaptiveCards.Templating

Note that the API has changed since this was posted.

var transformer = new AdaptiveTransformer();
var cardJson = transformer.Transform(templateJson, dataJson);

Please give it a shot and let us know how it works for you! Also, I will be updating the main issue, but official templating docs have been posted here: https://docs.microsoft.com/en-us/adaptive-cards/templating/

@rvinothrajendran

This comment has been minimized.

Copy link

commented Oct 9, 2019

@matthidinger transformer.Transform() API is working fine.
Waiting for AdaptiveCards.FromJson API in C# :)

@ThomasPe

This comment has been minimized.

Copy link

commented Oct 15, 2019

seems to be working fine for me as well.
the binding does not seem to work when the property name has a number in it, so Location1 & Location2 failed for me while LocationX & LocationY work fine. Not sure if that's a known issue.

@matthidinger

This comment has been minimized.

Copy link
Member Author

commented Oct 16, 2019

Hi @ThomasPe, that's an interesting topic we're evaluating right now: type coercion. Hopefully we'll have that up for review soon, but if you were able to share more about your template payload, data, and your opinion on the most reasonable expected behavior it would be great information for us as we evaluate proposals.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can鈥檛 perform that action at this time.