diff --git a/Node/cards-AdaptiveCards/.env b/Node/cards-AdaptiveCards/.env
new file mode 100644
index 0000000000..d5f29e6150
--- /dev/null
+++ b/Node/cards-AdaptiveCards/.env
@@ -0,0 +1,4 @@
+# Bot Framework Credentials
+
+MICROSOFT_APP_ID=
+MICROSOFT_APP_PASSWORD=
diff --git a/Node/cards-AdaptiveCards/README.md b/Node/cards-AdaptiveCards/README.md
new file mode 100644
index 0000000000..be5af93a12
--- /dev/null
+++ b/Node/cards-AdaptiveCards/README.md
@@ -0,0 +1,18 @@
+# Multi-Dialog Bot Sample
+
+A sample bot showing different kind of dialogs.
+
+[![Deploy to Azure][Deploy Button]][Deploy Node/AdaptiveCards]
+
+[Deploy Button]: https://azuredeploy.net/deploybutton.png
+[Deploy Node/AdaptiveCards]: https://azuredeploy.net
+
+### Prerequisites
+
+The minimum prerequisites to run this sample are:
+* Latest Node.js with NPM. Download it from [here](https://nodejs.org/en/download/).
+* The Bot Framework Emulator. To install the Bot Framework Emulator, download it from [here](https://emulator.botframework.com/). Please refer to [this documentation article](https://github.com/microsoft/botframework-emulator/wiki/Getting-Started) to know more about the Bot Framework Emulator.
+* **[Recommended]** Visual Studio Code for IntelliSense and debugging, download it from [here](https://code.visualstudio.com/) for free.
+
+### Code Highlights
+
diff --git a/Node/cards-AdaptiveCards/app.js b/Node/cards-AdaptiveCards/app.js
new file mode 100644
index 0000000000..ac63f11bb9
--- /dev/null
+++ b/Node/cards-AdaptiveCards/app.js
@@ -0,0 +1,266 @@
+// This loads the environment variables from the .env file
+require('dotenv-extended').load();
+
+var util = require('util');
+var builder = require('botbuilder');
+var restify = require('restify');
+
+// Setup Restify Server
+var server = restify.createServer();
+server.listen(process.env.port || process.env.PORT || 3978, function () {
+ console.log('%s listening to %s', server.name, server.url);
+});
+
+// Create chat bot and listen to messages
+var connector = new builder.ChatConnector({
+ appId: process.env.MICROSOFT_APP_ID,
+ appPassword: process.env.MICROSOFT_APP_PASSWORD
+});
+server.post('/api/messages', connector.listen());
+
+var bot = new builder.UniversalBot(connector, function (session) {
+
+ if (session.message && session.message.value) {
+ // receiving a card submit action
+ var value = session.message.value;
+
+ // Search, vlaidate parameters
+ if (value.type === 'hotelSearch') {
+ if (validateHotelSearch(session.message.value)) {
+ // proceed to search
+ return session.beginDialog('hotels-search', session.message.value);
+ }
+ }
+
+ // Hotel selection
+ if (value.type === 'hotelSelection') {
+ return sendHotelSelection(session, session.message.value);
+ }
+
+ // A form data was recieved, invalid or incomplete since the previous validation did not pass
+ return session.send('Please complete all the search parameters');
+ }
+
+ // Display Welcome card with Hotels and Flights search options
+ var card = {
+ 'contentType': 'application/vnd.microsoft.card.adaptive',
+ 'content': {
+ '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json',
+ 'type': 'AdaptiveCard',
+ 'version': '1.0',
+ 'body': [
+ {
+ 'type': 'Container',
+ 'speak': 'Hello!Are you looking for a flight or a hotel?',
+ 'items': [
+ {
+ 'type': 'ColumnSet',
+ 'columns': [
+ {
+ 'type': 'Column',
+ 'size': 'auto',
+ 'items': [
+ {
+ 'type': 'Image',
+ 'url': 'https://placeholdit.imgix.net/~text?txtsize=65&txt=Adaptive+Cards&w=300&h=300',
+ 'size': 'medium',
+ 'style': 'person'
+ }
+ ]
+ },
+ {
+ 'type': 'Column',
+ 'size': 'stretch',
+ 'items': [
+ {
+ 'type': 'TextBlock',
+ 'text': 'Hello!',
+ 'weight': 'bolder',
+ 'isSubtle': true
+ },
+ {
+ 'type': 'TextBlock',
+ 'text': 'Are you looking for a flight or a hotel?',
+ 'wrap': true
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ 'actions': [
+ // Hotels Search form
+ {
+ 'type': 'Action.ShowCard',
+ 'title': 'Hotels',
+ 'speak': 'Hotels',
+ 'card': {
+ 'type': 'AdaptiveCard',
+ 'body': [
+ {
+ 'type': 'TextBlock',
+ 'text': 'Welcome to the Hotels finder!',
+ 'speak': 'Welcome to the Hotels finder!',
+ 'weight': 'bolder',
+ 'size': 'large'
+ },
+ {
+ 'type': 'TextBlock',
+ 'text': 'Please enter your destination:'
+ },
+ {
+ 'type': 'Input.Text',
+ 'id': 'destination',
+ 'speak': 'Please enter your destination',
+ 'placeholder': 'Miami, Florida',
+ 'style': 'text'
+ },
+ {
+ 'type': 'TextBlock',
+ 'text': 'When do you want to check in?'
+ },
+ {
+ 'type': 'Input.Date',
+ 'id': 'checkin',
+ 'speak': 'When do you want to check in?'
+ },
+ {
+ 'type': 'TextBlock',
+ 'text': 'How many nights do you want to stay?'
+ },
+ {
+ 'type': 'Input.Number',
+ 'id': 'nights',
+ 'min': 1,
+ 'max': 60,
+ 'speak': 'How many nights do you want to stay?'
+ }
+ ],
+ 'actions': [
+ {
+ 'type': 'Action.Submit',
+ 'title': 'Search',
+ 'speak': 'Search',
+ 'data': {
+ 'type': 'hotelSearch'
+ }
+ }
+ ]
+ }
+ },
+ {
+ 'type': 'Action.ShowCard',
+ 'title': 'Flights',
+ 'speak': 'Flights',
+ 'card': {
+ 'type': 'AdaptiveCard',
+ 'body': [
+ {
+ 'type': 'TextBlock',
+ 'text': 'Flights is not implemented =(',
+ 'speak': 'Flights is not implemented',
+ 'weight': 'bolder'
+ }
+ ]
+ }
+ }
+ ]
+ }
+ };
+
+ var msg = new builder.Message(session)
+ .addAttachment(card);
+ session.send(msg);
+});
+
+// Search Hotels
+bot.dialog('hotels-search', require('./hotels-search'));
+
+// Help
+bot.dialog('support', require('./support'))
+ .triggerAction({
+ matches: [/help/i, /support/i, /problem/i]
+ });
+
+// log any bot errors into the console
+bot.on('error', function (e) {
+ console.log('And error ocurred', e);
+});
+
+function validateHotelSearch(hotelSearch) {
+ if (!hotelSearch) {
+ return false;
+ }
+
+ // Destination
+ var hasDestination = typeof hotelSearch.destination === 'string' && hotelSearch.destination.length > 3;
+
+ // Checkin
+ var checkin = Date.parse(hotelSearch.checkin);
+ var hasCheckin = !isNaN(checkin);
+ if (hasCheckin) {
+ hotelSearch.checkin = new Date(checkin);
+ }
+
+ // Nights
+ var nights = parseInt(hotelSearch.nights, 10);
+ var hasNights = !isNaN(nights);
+ if (hasNights) {
+ hotelSearch.nights = nights;
+ }
+
+ return hasDestination && hasCheckin && hasNights;
+}
+
+function sendHotelSelection(session, hotel) {
+ var description = util.format('%d stars with %d reviews. From $%d per night.', hotel.rating, hotel.numberOfReviews, hotel.priceStarting);
+ var card = {
+ 'contentType': 'application/vnd.microsoft.card.adaptive',
+ 'content': {
+ 'type': 'AdaptiveCard',
+ 'body': [
+ {
+ 'type': 'Container',
+ 'items': [
+ {
+ 'type': 'TextBlock',
+ 'text': hotel.name + ' in ' + hotel.location,
+ 'weight': 'bolder',
+ 'speak': '' + hotel.name + ''
+ },
+ {
+ 'type': 'TextBlock',
+ 'text': description,
+ 'speak': '' + description + ''
+ },
+ {
+ 'type': 'Image',
+ 'size': 'auto',
+ 'url': hotel.image
+ },
+ {
+ 'type': 'ImageSet',
+ 'imageSize': 'medium',
+ 'separation': 'strong',
+ 'images': hotel.moreImages.map((img) => ({
+ 'type': 'Image',
+ 'url': img
+ }))
+ }
+ ],
+ 'selectAction': {
+ 'type': 'Action.OpenUrl',
+ 'url': 'https://dev.botframework.com/'
+ }
+ }
+ ]
+ }
+ };
+
+ var msg = new builder.Message(session)
+ .addAttachment(card);
+
+ session.send(msg);
+}
\ No newline at end of file
diff --git a/Node/cards-AdaptiveCards/azuredeploy.json b/Node/cards-AdaptiveCards/azuredeploy.json
new file mode 100644
index 0000000000..b42df36f07
--- /dev/null
+++ b/Node/cards-AdaptiveCards/azuredeploy.json
@@ -0,0 +1,129 @@
+{
+ "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "siteName": {
+ "defaultValue": "BotBuilder-Samples",
+ "type": "string"
+ },
+ "hostingPlanName": {
+ "type": "string"
+ },
+ "siteLocation": {
+ "type": "string"
+ },
+ "sku": {
+ "type": "string",
+ "allowedValues": [
+ "Free",
+ "Shared",
+ "Basic",
+ "Standard"
+ ],
+ "defaultValue": "Free"
+ },
+ "workerSize": {
+ "type": "string",
+ "allowedValues": [
+ "0",
+ "1",
+ "2"
+ ],
+ "defaultValue": "0"
+ },
+ "repoUrl": {
+ "type": "string"
+ },
+ "branch": {
+ "type": "string"
+ },
+ "Project": {
+ "type": "string",
+ "defaultValue": "Node/cards-AdaptiveCards"
+ },
+ "WEBSITE_NODE_DEFAULT_VERSION": {
+ "type": "string",
+ "defaultValue": "6.9.5"
+ },
+ "MICROSOFT_APP_ID": {
+ "type": "string"
+ },
+ "MICROSOFT_APP_PASSWORD": {
+ "type": "string"
+ }
+ },
+ "resources": [
+ {
+ "apiVersion": "2014-06-01",
+ "name": "[parameters('hostingPlanName')]",
+ "type": "Microsoft.Web/serverFarms",
+ "location": "[parameters('siteLocation')]",
+ "properties": {
+ "name": "[parameters('hostingPlanName')]",
+ "sku": "[parameters('sku')]",
+ "workerSize": "[parameters('workerSize')]",
+ "numberOfWorkers": 1
+ }
+ },
+ {
+ "apiVersion": "2014-06-01",
+ "name": "[parameters('siteName')]",
+ "type": "Microsoft.Web/Sites",
+ "location": "[parameters('siteLocation')]",
+ "dependsOn": [
+ "[concat('Microsoft.Web/serverFarms/', parameters('hostingPlanName'))]"
+ ],
+ "tags": {
+ "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "empty"
+ },
+ "properties": {
+ "name": "[parameters('siteName')]",
+ "serverFarm": "[parameters('hostingPlanName')]"
+ },
+ "resources": [
+ {
+ "apiVersion": "2014-04-01",
+ "type": "config",
+ "name": "web",
+ "dependsOn": [
+ "[concat('Microsoft.Web/Sites/', parameters('siteName'))]"
+ ],
+ "properties": {
+ "appSettings": [
+ {
+ "name": "Project",
+ "value": "[parameters('Project')]"
+ },
+ {
+ "name": "WEBSITE_NODE_DEFAULT_VERSION",
+ "value": "[parameters('WEBSITE_NODE_DEFAULT_VERSION')]"
+ },
+ {
+ "name": "MICROSOFT_APP_ID",
+ "value": "[parameters('MICROSOFT_APP_ID')]"
+ },
+ {
+ "name": "MICROSOFT_APP_PASSWORD",
+ "value": "[parameters('MICROSOFT_APP_PASSWORD')]"
+ }
+ ]
+ }
+ },
+ {
+ "apiVersion": "2014-04-01",
+ "name": "web",
+ "type": "sourcecontrols",
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]",
+ "[concat('Microsoft.Web/Sites/', parameters('siteName'), '/config/web')]"
+ ],
+ "properties": {
+ "RepoUrl": "[parameters('repoUrl')]",
+ "branch": "[parameters('branch')]",
+ "IsManualIntegration": true
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Node/cards-AdaptiveCards/hotels-search.js b/Node/cards-AdaptiveCards/hotels-search.js
new file mode 100644
index 0000000000..df8f511434
--- /dev/null
+++ b/Node/cards-AdaptiveCards/hotels-search.js
@@ -0,0 +1,84 @@
+var util = require('util');
+var _ = require('lodash');
+var builder = require('botbuilder');
+var Store = require('./store');
+
+module.exports = function search(session, hotelSearch) {
+ var destination = hotelSearch.destination;
+ var checkIn = hotelSearch.checkin;
+ var checkOut = checkIn.addDays(hotelSearch.nights);
+
+ session.send(
+ 'Ok. Searching for Hotels in %s from %d/%d to %d/%d...',
+ destination,
+ checkIn.getMonth() + 1, checkIn.getDate(),
+ checkOut.getMonth() + 1, checkOut.getDate());
+
+ // Async search
+ Store
+ .searchHotels(destination, checkIn, checkOut)
+ .then(function (hotels) {
+ // Results
+ var title = util.format('I found in total %d hotels for your dates:', hotels.length);
+
+ var rows = _.chunk(hotels, 3).map(group =>
+ ({
+ 'type': 'ColumnSet',
+ 'columns': group.map(asHotelItem)
+ }));
+
+ var card = {
+ 'contentType': 'application/vnd.microsoft.card.adaptive',
+ 'content': {
+ 'type': 'AdaptiveCard',
+ 'body': [
+ {
+ 'type': 'TextBlock',
+ 'text': title,
+ 'size': 'extraLarge',
+ 'speak': '' + title + ''
+ }
+ ].concat(rows)
+ }
+ };
+
+ var msg = new builder.Message(session)
+ .addAttachment(card);
+ session.send(msg);
+ });
+
+ session.endDialog();
+};
+
+// Helpers
+function asHotelItem(hotel) {
+ return {
+ 'type': 'Column',
+ 'size': '20',
+ 'items': [
+ {
+ 'type': 'TextBlock',
+ 'horizontalAlignment': 'center',
+ 'wrap': false,
+ 'weight': 'bolder',
+ 'text': hotel.name,
+ 'speak': '' + hotel.name + ''
+ },
+ {
+ 'type': 'Image',
+ 'size': 'auto',
+ 'url': hotel.image
+ }
+ ],
+ 'selectAction': {
+ 'type': 'Action.Submit',
+ 'data': _.extend({ type: 'hotelSelection' }, hotel)
+ }
+ }
+}
+
+Date.prototype.addDays = function (days) {
+ var date = new Date(this.valueOf());
+ date.setDate(date.getDate() + days);
+ return date;
+};
\ No newline at end of file
diff --git a/Node/cards-AdaptiveCards/package.json b/Node/cards-AdaptiveCards/package.json
new file mode 100644
index 0000000000..389bd68001
--- /dev/null
+++ b/Node/cards-AdaptiveCards/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "botbuilder-sample-adaptivecards",
+ "version": "1.0.0",
+ "description": "Bot Builder Sample - Adaptive Cards",
+ "scripts": {
+ "start": "node app.js"
+ },
+ "author": "Microsoft Corp.",
+ "license": "MIT",
+ "keywords": [
+ "botbuilder",
+ "bots",
+ "chatbots",
+ "botbuilder-samples"
+ ],
+ "bugs": {
+ "url": "https://github.com/Microsoft/BotBuilder-Samples/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Microsoft/BotBuilder-Samples.git"
+ },
+ "dependencies": {
+ "bluebird": "^3.5.0",
+ "botbuilder": "^3.8.3",
+ "dotenv-extended": "^2.0.0",
+ "lodash": "^4.17.4",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Node/cards-AdaptiveCards/store.js b/Node/cards-AdaptiveCards/store.js
new file mode 100644
index 0000000000..d2c6df7547
--- /dev/null
+++ b/Node/cards-AdaptiveCards/store.js
@@ -0,0 +1,32 @@
+var Promise = require('bluebird');
+
+module.exports = {
+ searchHotels: function (destination, checkInDate, checkOutDate) {
+ return new Promise(function (resolve) {
+
+ // Filling the hotels results manually just for demo purposes
+ var hotels = [];
+ for (var i = 1; i <= 6; i++) {
+ hotels.push({
+ name: 'Hotel ' + i,
+ location: destination,
+ rating: Math.ceil(Math.random() * 5),
+ numberOfReviews: Math.floor(Math.random() * 5000) + 1,
+ priceStarting: Math.floor(Math.random() * 450) + 80,
+ image: 'https://placeholdit.imgix.net/~text?txtsize=35&txt=Hotel+' + i + '&w=500&h=260',
+ moreImages: [
+ 'https://placeholdit.imgix.net/~text?txtsize=65&txt=Pic+1&w=450&h=300',
+ 'https://placeholdit.imgix.net/~text?txtsize=65&txt=Pic+2&w=450&h=300',
+ 'https://placeholdit.imgix.net/~text?txtsize=65&txt=Pic+3&w=450&h=300',
+ 'https://placeholdit.imgix.net/~text?txtsize=65&txt=Pic+4&w=450&h=300'
+ ]
+ });
+ }
+
+ hotels.sort(function (a, b) { return a.priceStarting - b.priceStarting; });
+
+ // complete promise with a timer to simulate async response
+ setTimeout(function () { resolve(hotels); }, 1000);
+ });
+ }
+};
\ No newline at end of file
diff --git a/Node/cards-AdaptiveCards/support.js b/Node/cards-AdaptiveCards/support.js
new file mode 100644
index 0000000000..72b9d0adcf
--- /dev/null
+++ b/Node/cards-AdaptiveCards/support.js
@@ -0,0 +1,13 @@
+module.exports = function (session) {
+ // Generate ticket
+ var tickerNumber = Math.ceil(Math.random() * 20000);
+
+ // Reply and return to parent dialog
+ session.send('Your message \'%s\' was registered. Once we resolve it; we will get back to you.', session.message.text);
+
+ session.send('Thanks for contacting our support team. Your ticket number is %s.', tickerNumber);
+
+ session.endDialogWithResult({
+ response: tickerNumber
+ });
+};
\ No newline at end of file