PROBLEM
Unavailable variant combinations are different from sold-out because customers don't understand it's the selects which make certain combinations 'not possible'.
Shopify's way of handling this is to display 'Unavailable' in the buy button. But customers think this means sold-out when in reality, they've chosen the wrong combination of variants...
The previous JS workarounds to remove unavailable or 'not possible' variants don't work in Shopify 2.0's new default/flagship theme, Dawn because the JS is different.
As far as I can tell, Dawn's variant JS was recently moved from /asstes/variants.js to line 497 in /assets/global.js.
SKILL
My CSS is decent but my JS is lame, I'm a designer sorry.
QUESTIONS
Based on user interaction with the first variant, how do you hide unavailable variants (not sold-out) in Shopify 2.0 Dawn?
How do you make one variant option set a checkbox instead of a radio button or radio?
What's the best way to add custom text as the first option in selects? e.g. 'Choose a size...' or 'Choose a color...' etc. Is it best to hard-code or use JS for this as well?
RESOURCES/EXAMPLES
Here's a pull request which grabs sold-out from the new Dawn JS but I don't understand how to adapt it for 'Unavailable' sorry (which is a different exception from sold-out): https://github.com/Shopify/dawn/pull/105
Here's an example of how to hide unavailable variants in the older Debut theme which doesnβt seem to work in the newer Dawn JS: https://www.youtube.com/watch?v=vspWDu_POYA
Here's a link to the JS gist referenced in that video: https://gist.github.com/jonathanmoore/c0e0e503aa732bf1c05b7a7be4230c61
And finally, here's the new code from Dawn at line 497 in /assets/global.js
class VariantSelects extends HTMLElement {
constructor() {
super();
this.addEventListener('change', this.onVariantChange);
}
onVariantChange() {
this.updateOptions();
this.updateMasterId();
this.toggleAddButton(true, '', false);
this.updatePickupAvailability();
if (!this.currentVariant) {
this.toggleAddButton(true, '', true);
this.setUnavailable();
} else {
this.updateMedia();
this.updateURL();
this.updateVariantInput();
this.renderProductInfo();
}
}
updateOptions() {
this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
}
updateMasterId() {
this.currentVariant = this.getVariantData().find((variant) => {
return !variant.options.map((option, index) => {
return this.options[index] === option;
}).includes(false);
});
}
updateMedia() {
if (!this.currentVariant || !this.currentVariant?.featured_media) return;
const newMedia = document.querySelector(
`[data-media-id="${this.dataset.section}-${this.currentVariant.featured_media.id}"]`
);
if (!newMedia) return;
const parent = newMedia.parentElement;
parent.prepend(newMedia);
window.setTimeout(() => { parent.scroll(0, 0) });
}
updateURL() {
if (!this.currentVariant) return;
window.history.replaceState({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
}
updateVariantInput() {
const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
productForms.forEach((productForm) => {
const input = productForm.querySelector('input[name="id"]');
input.value = this.currentVariant.id;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
}
updatePickupAvailability() {
const pickUpAvailability = document.querySelector('pickup-availability');
if (!pickUpAvailability) return;
if (this.currentVariant?.available) {
pickUpAvailability.fetchAvailability(this.currentVariant.id);
} else {
pickUpAvailability.removeAttribute('available');
pickUpAvailability.innerHTML = '';
}
}
renderProductInfo() {
fetch(`${this.dataset.url}?variant=${this.currentVariant.id}§ion_id=${this.dataset.section}`)
.then((response) => response.text())
.then((responseText) => {
const id = `price-${this.dataset.section}`;
const html = new DOMParser().parseFromString(responseText, 'text/html')
const destination = document.getElementById(id);
const source = html.getElementById(id);
if (source && destination) destination.innerHTML = source.innerHTML;
document.getElementById(`price-${this.dataset.section}`)?.classList.remove('visibility-hidden');
this.toggleAddButton(!this.currentVariant.available, window.variantStrings.soldOut);
});
}
toggleAddButton(disable = true, text, modifyClass = true) {
const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
if (!addButton) return;
if (disable) {
addButton.setAttribute('disabled', true);
if (text) addButton.textContent = text;
} else {
addButton.removeAttribute('disabled');
addButton.textContent = window.variantStrings.addToCart;
}
if (!modifyClass) return;
}
setUnavailable() {
const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
if (!addButton) return;
addButton.textContent = window.variantStrings.unavailable;
document.getElementById(`price-${this.dataset.section}`)?.classList.add('visibility-hidden');
}
getVariantData() {
this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
return this.variantData;
}
}
customElements.define('variant-selects', VariantSelects);
class VariantRadios extends VariantSelects {
constructor() {
super();
}
updateOptions() {
const fieldsets = Array.from(this.querySelectorAll('fieldset'));
this.options = fieldsets.map((fieldset) => {
return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
});
}
}
customElements.define('variant-radios', VariantRadios);
Any help or pointers in the right direction would be much appreciated. Cheers
I am using the Geolocation functionality of Xamarin Essentials in my app as a foreground service running all the time and I have noticed on several devices that I sometimes get locations which are really far away from where they should actually be, looking like this (the marked location is far away from the real location):
public async Task Run(CancellationToken token)
{
await Task.Run(async () => {
while (!stopping)
{
token.ThrowIfCancellationRequested();
try
{
await Task.Delay(2000);
var request = new GeolocationRequest(GeolocationAccuracy.Best);
var location = await Geolocation.GetLocationAsync(request);
if (location != null)
{
var message = new LocationMessage
{
Latitude = location.Latitude,
Longitude = location.Longitude
};
Device.BeginInvokeOnMainThread(() =>
{
MessagingCenter.Send<LocationMessage>(message, "Location");
});
}
}
catch (Exception ex)
{
Device.BeginInvokeOnMainThread(() =>
{
var errormessage = new LocationErrorMessage();
MessagingCenter.Send<LocationErrorMessage>(errormessage, "LocationError");
});
}
}
return;
}, token);
}
result
I'm trying to get my feet wet with Xamarin and I'm having trouble adding in my organization's login. The screen shot below is as far as I can get attempting to login. When I click "Continue" the same page just loads again. Not really sure what's going on.
The image is the screen I'm stuck on.
I've added code that represents the app class and the code behind for the XAML page attempting to login, leaving out what I "think" is irrelvant.
Any suggestions?
public partial class App : Application
{
public static string AzureBackendUrl =
DeviceInfo.Platform == DevicePlatform.Android ? "http://10.0.2.2:5000" : "http://localhost:5000";
public static bool UseMockDataStore = true;
public static IPublicClientApplication PCA = null;
public static string ClientID = "CLIENT_ID";
public static string[] Scopes = { "User.Read" };
public static string Username = string.Empty;
public static object ParentWindow { get; set; }
public App()
{
InitializeComponent();
if (UseMockDataStore)
DependencyService.Register<MockDataStore>();
else
DependencyService.Register<AzureDataStore>();
PCA = PublicClientApplicationBuilder.Create(ClientID)
.WithRedirectUri($"msal{App.ClientID}://auth")
//.WithParentActivityOrWindow(() => App.ParentWindow)
.Build();
MainPage = new MSAL_Example();
}
}
public partial class MSAL_Example : ContentPage
{
public static string tenant_name = "MY_TENANT_NAME";
public MSAL_Example()
{
InitializeComponent();
App.ParentWindow = this;
}
public async Task SignOutAsync()
{
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
try
{
while (accounts.Any())
{
await App.PCA.RemoveAsync(accounts.FirstOrDefault());
accounts = await App.PCA.GetAccountsAsync();
}
slUser.IsVisible = false;
Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign in"; });
}
catch (Exception ex)
{
Debug.WriteLine("\tERROR {0}", ex.Message);
}
}
public async Task SignInAsync()
{
AuthenticationResult authResult = null;
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
// let's see if we have a user in our belly already
try
{
IAccount firstAccount = accounts.FirstOrDefault();
authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
.ExecuteAsync();
await RefreshUserDataAsync(authResult.AccessToken).ConfigureAwait(false);
Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign out"; });
}
catch (MsalUiRequiredException ex)
{
try
{
authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow)
.WithAuthority("https://login.microsoftonline.com/" + tenant_name)
.ExecuteAsync();
await RefreshUserDataAsync(authResult.AccessToken);
Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign out"; });
}
catch (Exception ex2)
{
Debug.WriteLine("\tERROR {0}", ex2.Message);
}
}
}
public async Task RefreshUserDataAsync(string token)
{
//get data from API
HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/me");
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token);
HttpResponseMessage response = await client.SendAsync(message);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
JObject user = JObject.Parse(responseString);
slUser.IsVisible = true;
Device.BeginInvokeOnMainThread(() =>
{
lblDisplayName.Text = user["displayName"].ToString();
lblGivenName.Text = user["givenName"].ToString();
lblId.Text = user["id"].ToString();
lblSurname.Text = user["surname"].ToString();
lblUserPrincipalName.Text = user["userPrincipalName"].ToString();
// just in case
btnSignInSignOut.Text = "Sign out";
});
}
else
{
await DisplayAlert("Something went wrong with the API call", responseString, "Dismiss");
}
}
}
I have created a bot locally using bot framework v4 c#. It has a welcome card that automatically pops up as soon I connect my local url with emulator, but recently I deployed my bot on azure and integrated it using direct line channel in my website. Now whenever I click, it opens the bot but the welcome card does not come on its own ,it appears when I write something from my chatbot. I just want the welcome card to appaer automatically as it appears in the emulator. Guys can you help me out please? Below is the code of direct line which I am integrating in my website.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<!-- Paste line 7 to 27 after the title tag in _Layout.cshtml -->
<link href="https://cdn.botframework.com/botframework-webchat/latest/botchat.css" rel="stylesheet"
/>
<script src="https://cdn.botframework.com/botframework-webchat/latest/botchat.js"></script>
<style>
#mychat {
margin: 10px;
position: fixed;
bottom: 30px;
left: 10px;
z-index: 1000000;
}
.botIcon {
float: left !important;
border-radius: 50%;
}
.userIcon {
float: right !important;
border-radius: 50%;
}
</style>
</head>
< body>
<!-- Paste line from 31 to 33 before the </body> tag at the end of code -->
<div id="container">
<img id="mychat" src=""/>
</div>
</body>
<!-- Paste line 38 to 88 after the </html> tag -->
<script>
(function () {
var div = document.createElement("div");
var user = {
id: "",
name: ''
};
var bot = {
id: '',
name: 'SaathiBot'
};
const botConnection = new BotChat.DirectLine({
secret: '',
webSocket: false
})
document.getElementsByTagName('body')[0].appendChild(div);
div.outerHTML = "<div id='botDiv' style='width: 400px; height: 0px; margin:10px; position:
fixed; bottom: 0; left:0; z-index: 1000;><div id='botTitleBar' style='height: 40px; width: 400px;
position:fixed; cursor: pointer;'>";
BotChat.App({
botConnection: botConnection,
user: user,
bot: bot
}, document.getElementById("botDiv"));
document.getElementsByClassName("wc-header")[0].setAttribute("id", "chatbotheader");
document.querySelector('body').addEventListener('click', function (e) {
e.target.matches = e.target.matches || e.target.msMatchesSelector;
if (e.target.matches('#chatbotheader')) {
var botDiv = document.querySelector('#botDiv');
botDiv.style.height = "0px";
document.getElementById("mychat").style.display = "block";
};
});
document.getElementById("mychat").addEventListener("click", function (e) {
document.getElementById("botDiv").style.height = '500px';
e.target.style.display = "none";
})
}());
</script>
Also here is my welcome card code in c#
namespace Microsoft.BotBuilderSamples
{
public class WelcomeUser : SaathiDialogBot<MainDialog>
{
protected readonly string[] _cards =
{
Path.Combine(".", "Resources", "WelcomeCard.json"),
};
public WelcomeUser(ConversationState conversationState, UserState userState, MainDialog dialog, ILogger<SaathiDialogBot<MainDialog>> logger)
: base(conversationState, userState, dialog, logger)
{
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
await SendWelcomeMessageAsync(turnContext, cancellationToken);
Random r = new Random();
var cardAttachment = CreateAdaptiveCardAttachment(_cards[r.Next(_cards.Length)]);
await turnContext.SendActivityAsync(MessageFactory.Attachment(cardAttachment), cancellationToken);
}
private static async Task SendWelcomeMessageAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
if (DateTime.Now.Hour < 12)
{
await turnContext.SendActivityAsync(
$"Hi,Good Morning {member.Name}",
cancellationToken: cancellationToken);
}
else if (DateTime.Now.Hour < 17)
{
await turnContext.SendActivityAsync(
$"Hi,Good Afternoon {member.Name}",
cancellationToken: cancellationToken);
}
else
{
await turnContext.SendActivityAsync(
$"Hi,Good Evening {member.Name}",
cancellationToken: cancellationToken);
}
}
}
}
private static Attachment CreateAdaptiveCardAttachment(string filePath)
{
var adaptiveCardJson = File.ReadAllText(filePath);
var adaptiveCardAttachment = new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCardJson),
};
return adaptiveCardAttachment;
}
}
}
Here the saathiDialog code which inherited in welcome card. These are the two files inside my bot folder
public class SaathiDialogBot<T> : ActivityHandler where T : Dialog
{
protected readonly BotState ConversationState;
protected readonly Dialog Dialog;
protected readonly ILogger Logger;
protected readonly BotState UserState;
private DialogSet Dialogs { get; set; }
public SaathiDialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<SaathiDialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = turnContext.Activity;
if (string.IsNullOrWhiteSpace(activity.Text) && activity.Value != null)
{
activity.Text = JsonConvert.SerializeObject(activity.Value);
}
if (turnContext.Activity.Text == "Yes")
{
await turnContext.SendActivityAsync($"Good bye. I will be here if you need me. ", cancellationToken: cancellationToken);
await turnContext.SendActivityAsync($"Say Hi to wake me up.", cancellationToken: cancellationToken);
}
else
{
await base.OnTurnAsync(turnContext, cancellationToken);
}
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
}
here is main Dialog code
namespace Microsoft.BotBuilderSamples
{
public class MainDialog : ComponentDialog
{
private const string UserInfo = "value-userInfo";
protected readonly ILogger _logger;
protected readonly string[] _cards =
{
Path.Combine(".", "Resources", "ValidationCard.json"),
};
public MainDialog(ILogger<MainDialog> logger) : base(nameof(MainDialog))
{
_logger = logger;
AddDialog(new ProductIssue($"{nameof(MainDialog)}.fromMain"));
AddDialog(new ProductIssue($"{nameof(Confirm)}.fromConfirm"));
AddDialog(new ProductIssue($"{ nameof(Resolution)}.resolution"));
AddDialog(new Confirm());
AddDialog(new ChoicePrompt($"{nameof(MainDialog)}.issue"));
AddDialog(new TextPrompt($"{nameof(MainDialog)}.callDialog"));
AddDialog(new NumberPrompt<int>($"{nameof(MainDialog)}.num", valinatiotionAsync));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", new WaterfallStep[]
{
MoblieNumberAsync,
ChoiceCardStepAsync,
ShowCardStepAsync,
CallingDialogsAsync
}));
AddDialog(new TextPrompt(nameof(TextPrompt)));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> MoblieNumberAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values[UserInfo] = new UserInput();
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Kindly enter your 10 digit mobile number without any spaces, dashes and country code. We will be sending an OTP later to this number "),
RetryPrompt = MessageFactory.Text("Incorrect mobile number entered. Please only enter the 10 digits of your mobile without any spaces, dashes and country code.")
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.num", options, cancellationToken);
}
private async Task<DialogTurnResult> ChoiceCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
BotAPIBLL botApiBLL = new BotAPIBLL();
var response = botApiBLL.GetCustomerDetail(stepContext.Context.Activity.Text);
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Fetching your details from our systems. This may take a moment"), cancellationToken);
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Welcome user, How can we serve you ? "),
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a option between 1 to 4."),
Choices = GetChoices(),
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.issue", options, cancellationToken);
}
private async Task<DialogTurnResult> ShowCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var attachments = new List<Attachment>();
var reply = MessageFactory.Text("");
var user_choice = ((FoundChoice)stepContext.Result).Value;
switch (user_choice)
{
case "Product issue":
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
break;
case "Register Product":
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
case "Online Purchase":
reply.Attachments.Add(Cards.GetHeroCard2().ToAttachment());
break;
case "Customer Grivance":
reply.Attachments.Add(Cards.GetHeroCard3().ToAttachment());
break;
default:
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
}
if (user_choice == "Register Product" || user_choice == "Online Purchase" || user_choice == "Customer Grivance")
{
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
Random r = new Random();
var validationcard = Cards.CreateAdaptiveCardAttachment2(_cards[r.Next(_cards.Length)]);
await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(validationcard), cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
else
{
var options2 = new PromptOptions() { Prompt = reply, RetryPrompt = MessageFactory.Text("Retry") };
return await stepContext.PromptAsync($"{nameof(MainDialog)}.callDialog", options2, cancellationToken);
}
}
private async Task<DialogTurnResult> CallingDialogsAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
string choice = stepContext.Result.ToString();
if (choice.ToLower() == "no")
{
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.fromMain", null, cancellationToken);
}
else
return await stepContext.BeginDialogAsync(nameof(Confirm), null, cancellationToken);
}
private IList<Choice> GetChoices()
{
var cardOptions = new List<Choice>()
{
new Choice() { Value = "Product issue", Synonyms = new List<string>() { "adaptive" } },
new Choice() { Value = "Register Product", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Online Purchase", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Customer Grivance", Synonyms = new List<string>() { "hero" } },
};
return cardOptions;
}
private Task<bool> valinatiotionAsync(PromptValidatorContext<int> promptContext, CancellationToken cancellationToken)
{
string value = (string)promptContext.Context.Activity.Text;
if (Regex.IsMatch(value, "^[0-9]{10}$"))
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(false);
}
}
}
}
You have to use the ConversationUpdate event ActivityTypes.ConversationUpdate
This is an example inside of a Switch statement where i check the different ActivityTypes i get, and the conversationUpdate is used to show the welcome message
case ActivityTypes.ConversationUpdate:
{
if (activity.MembersAdded?.Count > 0)
{
await innerDc.BeginDialogAsync(nameof(dialog));
}
break;
}
UPDATE
Look at this working example and explanation on how to send a Welcome Message Event from WebChat.
If you are using WebChat or directline, the botβs ConversationUpdate is sent when the conversation is created and the user sidesβ ConversationUpdate is sent when they first send a message. When ConversationUpdate is initially sent, there isnβt enough information in the message to construct the dialog stack. The reason that this appears to work in the emulator, is that the emulator simulates a sort of pseudo DirectLine, but both conversationUpdates are resolved at the same time in the emulator, and this is not the case for how the actual service performs.
A workaround would be to send a back channel welcome event to the bot when the DirectLine connection is established and send a welcome message from the onEventAsync handler instead of onMembersAdded.
Embedded HTML for Web Chat
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<style>
#webchat {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div style="display: flex">
<div style="position: relative; height: 500px; width: 500px"><div id="bot" ></div></div>
</div>
<script>
(async function() {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
var userinfo = {
id: 'user-id',
name: 'user name',
locale: 'es'
};
var botConnection = new window.BotChat.DirectLine({ token });
botConnection.connectionStatus$
.subscribe(connectionStatus => {
switch(connectionStatus) {
case window.BotChat.ConnectionStatus.Online:
botConnection.postActivity({
from: { id: 'myUserId', name: 'myUserName' },
type: 'event',
name: 'webchat/join',
value: { locale: 'en-US' }
}).subscribe(
id => console.log("Posted welcome event, assigned ID ", id),
error => console.log("Error posting activity", error)
);
break;
}
});
BotChat.App({
botConnection: botConnection,
user: userinfo,
bot: { id: 'botid' },
resize: 'detect'
}, document.getElementById("bot"));
})().catch(err => console.log(err));
</script>
</body>
</html>
Bot Code in C#
protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Name == "webchat/join") {
await turnContext.SendActivityAsync("Welcome Message!");
}
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.ChannelId != "webchat" && turnContext.Activity.ChannelId != "directline") {
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync($"Hi there - {member.Name}. {WelcomeMessage}", cancellationToken: cancellationToken);
await turnContext.SendActivityAsync(InfoMessage, cancellationToken: cancellationToken);
await turnContext.SendActivityAsync(PatternMessage, cancellationToken: cancellationToken);
}
}
}
}
Hope this helps.
I'm new in Bot framework and I face this issue:
I want to move from dialog to another dialog, the callback function is working for context.Call but not for context.Forward?
I try multiple solutions such as put this.CallbackFunctionName but it dosen't work.
Here is my code for call new dialog:
switch (submitType)
{
case "alarm":
context.Call(new AlarmDialog(), ResumeAfterAlarmDialog);
context.Done(true);
return;
case "game":
await context.Forward(new AlarmDialog(), ResumeAfterAlarmDialog, value, CancellationToken.None);
return;
}
And here the method that I call:
private async Task ResumeAfterAlarmDialog(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync($"You are finish the alarm dialog");
context.Wait(this.MessageReceivedAsync);
}
And this is the error that I have for the context.Forward:
cannot use a method group as an argument to a dynamically dispatched operation.Did you intend to invoke the method?
Here the full implementation of the class:
namespace CardEx.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity.Value != null)
{
// Got an Action Submit
dynamic value = activity.Value;
string submitType = value.Type.ToString();
switch (submitType)
{
case "alarm":
context.Call(new AlarmDialog(), ResumeAfterAlarmDialog);
return;
case "alarm2":
await context.Forward(new AlarmDialog(), ResumeAfterAlarmDialog, value, CancellationToken.None);
return;
}
}
AdaptiveCard aCard = new AdaptiveCard()
{
Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "Welcome!",
Weight = AdaptiveTextWeight.Bolder,
Size = AdaptiveTextSize.Large
},
new AdaptiveTextBlock() { Text = "Please choose one of the following:" },
},
Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Title = "Set an alarm",
DataJson = "{ \"Type\": \"alarm\" }"
},
new AdaptiveSubmitAction()
{
Title = "Play a game",
DataJson = "{ \"Type\": \"game\" }"
}
}
};
Attachment attachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = aCard
};
var reply = context.MakeMessage();
reply.Attachments.Add(attachment);
await context.PostAsync(reply, CancellationToken.None);
context.Wait(MessageReceivedAsync);
}
private async Task ResumeAfterAlarmDialog(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync($"You are finish the alarm dialog");
context.Wait(this.MessageReceivedAsync);
}
}
}
Thank you.
cannot use a method group as an argument to a dynamically dispatched operation.Did you intend to invoke the method?
the parameter value used in context.Forward is a dynamic type, which raised this issue.
Please try following code:
await context.Forward(new TestDialog(), ResumeAfterAlarmDialog, (object)value, CancellationToken.None);