I would like to remove the "No Preference" only in the "Change prompt" in the formflow or at least change it text only for the confirmation prompt leaving the of the form with "No Preference" option.
I was able to change its text, but it changed the whole form and didn't work for me.
public static IForm<PromoBot> BuildForm()
var form = new FormBuilder<PromoBot>()
.Message("Hi......!")
.Field(nameof(Nome), validate: async (state, value) =>
{
..........
result.IsValid = true;
return result;
}, active: state => true)
.Message("xxxxxx")
.Field(nameof(CEP)
.Field(nameof(Estados))
.Confirm(async (state) =>
{
return new PromptAttribute("You have selected the following: \n {*} "Is this correct?{||}");
})
.Message("Excelente! Agora vou precisar de alguns segundos para encontrar o melhor plano para sua empresa… já volto!")
.OnCompletion(OnComplete);
var noPreferenceStrings = new string[] { "New Text" };
form.Configuration.Templates.Single(t => t.Usage == TemplateUsage.NoPreference).Patterns = noPreferenceStrings;
form.Configuration.NoPreference = noPreferenceStrings;
return form.Build();
}
Ordinarily you can use a template attribute to modify FormFlow's behavior, but the navigation step is sort of finicky. I think the best thing to do in this situation is provide your form with a custom prompter.
In your case, the code could look something like this:
public static IForm<MyClass> BuildForm()
{
var formBuilder = new FormBuilder<MyClass>()
.Field(nameof(FirstName))
.Field(nameof(LastName))
.Confirm("Is this okay? {*}")
.Prompter(PromptAsync)
;
return formBuilder.Build();
}
/// <summary>
/// Here is the method we're using for the PromptAsyncDelgate.
/// </summary>
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt,
MyClass state, IField<MyClass> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
// Check to see if the form is on the navigation step
if (field.Name.Contains("navigate") && prompt.Buttons.Any())
{
// If it's on the navigation step,
// we want to change or remove the No Preference line
if (you_want_to_change_it)
{
var noPreferenceButton = prompt.Buttons.Last();
// Make sure the Message stays the same or else
// FormFlow won't know what to do when this button is clicked
noPreferenceButton.Message = noPreferenceButton.Description;
noPreferenceButton.Description = "Back";
}
else if(you_want_to_remove_it)
{
prompt.Buttons.RemoveAt(prompt.Buttons.Count - 1);
}
}
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
await context.PostAsync(promptMessage);
return prompt;
}
One additional note: "Back" is actually a special command in FormFlow. Whereas "No Preference" will take you back to the confirmation step, "Back" will take you to the last field in the form. If you want to actually put a back button in your navigation step, you can leave out this line:
noPreferenceButton.Message = noPreferenceButton.Description;
Related
I am trying to design a custom dialogbox in unity for runtime on all platforms (in my case for Android and webplayer).
I have succeeded in displaying a dialog and get the user response and updating this response. But I'm lost on how to call display the dialog box and wait before it returns a value when using it externally from some other method / class.
This is my code to display the dialogbox:
public void ShowDialogBox(string title, string message, DialogBoxButtons buttons)
{
StartCoroutine(ShowDialogBoxHelper(title, message, buttons));
}
public void ShowDialogBox(string title, string message)
{
StartCoroutine(ShowDialogBoxHelper(title, message, DialogBoxButtons.OK));
}
public IEnumerator ShowDialogBoxHelper (string title, string message, DialogBoxButtons buttons)
{
Response = DialogResponse.NONE;
Title.text = title;
Message.text = message;
ButtonSet = buttons;
HandleButtonSet(ButtonSet);
_canvasGroup.alpha = 1;
_canvasGroup.interactable = true;
transform.SetAsLastSibling();
_apprearenceMode = ApprearenceMode.Shown;
yield return StartCoroutine(WaitForButtonResponse());
Debug.Log("user response : " + Response);
}
The Coroutine "WaitForButtonResponse()" is declared so:
IEnumerator WaitForButtonResponse()
{
Debug.Log("waiting in the enumerator, responded: " + Response);
yield return new waitForUserAction(() => Response != DialogResponse.NONE);
Debug.Log("done waiting in the enumerator, responded: " + Response);
}
And the "waitForUserAction()" coroutine is a custom one, inheriting from CustomYieldInstruction
[System.Serializable]
class waitForUserAction : CustomYieldInstruction
{
Func<bool> m_Predicate;
public override bool keepWaiting { get { return !m_Predicate(); } }
public waitForUserAction(Func<bool> predicate) { m_Predicate = predicate; }
}
When I call the ShowDialogBox method, the dialogbox appears as expected and when I click on one of the options, the response is correctly updated. But, If I want the ShowDialogBox to return a response and wait till the user has clicked on a dialog button before returning, how can I achieve it?
Desired behaviour:
public DialogResponse ShowDialogBox(string title, string message, DialogBoxButtons buttons)
{
StartCoroutine(ShowDialogBoxHelper(title, message, buttons));
return Response;
}
and usage like this:
if (ShowDialogBox("test", "testing dialog box behaviour", DialogBoxButtons.YES_NO_CANCEL) == DialogResponse.YES)
{
//do something
}
The problem now is that, "return Response;" does not wait for the "StartCoroutine(ShowDialogBoxHelper(title, message, buttons));" to update the user's choice and returns the old value of Response rather than what the user currently chooses.
Thank you in advance for any help in this regard!
Cheers,
Ani
Instead of immediately evaluate the result you would need to actually wait and tell the routine what to do when a response is there.
I would use an Action<DialogResponse> for that like:
// Here you can actually use an overload with optional parameter
// If you don't pass in the "buttons" it simply has the default value `OK`
// And additionally pass in the action to invoke once a response was given
// If you don't pass it then simply nothing happens ;)
public void ShowDialogBox(string title, string message, DialogBoxButtons buttons = DialogBoxButtons.OK, Action<DialogResponse> onResponse = null)
{
StartCoroutine(ShowDialogBoxHelper(title, message, buttons, onResponse));
}
public IEnumerator ShowDialogBoxHelper (string title, string message, DialogBoxButtons buttons, Action<DialogResponse> onResponse)
{
Response = DialogResponse.NONE;
Title.text = title;
Message.text = message;
ButtonSet = buttons;
HandleButtonSet(ButtonSet);
_canvasGroup.alpha = 1;
_canvasGroup.interactable = true;
transform.SetAsLastSibling();
_apprearenceMode = ApprearenceMode.Shown;
// Here rather use the Unity built-in
yield return new WaitWhile(() => Response == DialogResponse.NONE);
Debug.Log("user response : " + Response);
onResponse?.Invoke(Response);
}
This you would use either with a lambda expression like e.g.
ShowDialogBox("test", "testing dialog box behaviour", DialogBoxButtons.YES_NO_CANCEL, response =>
{
if(response == DialogResponse.YES)
{
// Do something
}
});
or the same but with a method
ShowDialogBox("test", "testing dialog box behaviour", DialogBoxButtons.YES_NO_CANCEL, HandleResponse);
private void HandleResponse(DialogResponse response)
{
if(response == DialogResponse.YES)
{
// Do something
}
}
Say example when we have 3 menu( choices ) within a waterfall dialog in an initial step, when user selected third choice, the system should take over to qna bot form that , is that something possible ?
Nodejs here instead of C# but hopefully this will point you in the right direction. You can do this, you just need to create a separate QnA Dialog and call it from within your waterfall via await step.beginDialog(YOUR_QNA_DIALOG_NAME). You would want that dialog to prompt for the question in the first step, provide the answer in the second step, and (if desired) prompt if they want to ask another question so you can loop via replaceDialog. If there are steps after the initial menu selection, you may wish to cancellAllDialogs instead of endDialog when they exit, otherwise the bot will pick up where it left off with the first dialog.
There might be a more elegant way to do this without using a waterfall dialog but I haven't found it yet.
I have provided a sample QnA Dialog below, but note that in this case I am using it as a default action if no intent is recognized, so it's using the activity.text from the ActivityHandler instead of explicitly prompting the user, and it does not loop. Still, I though this might be helpful.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { Dialog, MessageFactory } = require('botbuilder');
const { QnAServiceHelper } = require('../helpers/qnAServiceHelper');
const { CardHelper} = require('../helpers/cardHelper');
class QnADialog {
constructor() {
}
async processAsync(oldState, activity){
const defaultAnswer = `I'm sorry, I don't know how to help with that. Try asking a different question or type "Help" for options.`;
var MINIMUM_SCORE = 50;
var newState = null;
var query = activity.text;
var qnaResult = await QnAServiceHelper.queryQnAService(query, oldState);
var qnaAnswer = qnaResult[0].answer;
var prompts = null;
if(qnaResult[0].context != null){
prompts = qnaResult[0].context.prompts;
}
var outputActivity = null;
if (prompts == null || prompts.length < 1) {
if (qnaResult[0].score > MINIMUM_SCORE) {
outputActivity = MessageFactory.text(qnaAnswer);
} else {
outputActivity = MessageFactory.text(defaultAnswer);
}
}
else {
var newState = {
PreviousQnaId: qnaResult[0].id,
PreviousUserQuery: query
}
outputActivity = CardHelper.GetHeroCard('', qnaAnswer, prompts);
}
return [newState, outputActivity , null];
}
}
module.exports.QnADialog = QnADialog;
BuildForm Method
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
.Field(new FieldReflector<FAQConversation>(nameof(Inquiry))
.SetValidate(AnswerInquiry)
.SetPrompt(new PromptAttribute("Okay, tell me what is your question. Enter \"back\" to go back to Products Selection."))
)
.Build();
}
Validation Method
private static async Task<ValidateResult> AnswerInquiry(FAQConversation state, object value)
{
var result = new ValidateResult();
//somecode here
if(testCase == false)
{
result.isValid = false;
result.Feedback = "Try again";
}
else
{
result.isValid = true;
}
return result;
}
My validation method returns the feedback "Try Again" text when the input on my validating field is invalid. However, it is returning both Original Prompt and the Feedback text.
Question
How do I remove the original prompt on revalidation of a field?
While FormFlow does offer a lot of customizability, the main idea behind it is to automate everything for you, which tends to indicate that at least some things are built in pretty strongly.
I understand that what you want to do is disable the prompt for a field upon "retry," which is to say that if the user was already shown the prompt for a field and they entered something invalid then they shouldn't be shown the prompt again. I can see in the source code that FormFlow doesn't really provide a special case for "retries" and the behavior of prompting when a field remains unknown is one of those built-in things. However, there is still something you can do.
FormFlow offers a (largely undocumented) way to replace what's called the "prompter." You can do this using the Prompter() method, which takes a PromptAsyncDelegate. As a starting point for your new prompter, you can find the default prompter in the FormBuilder source code:
_form._prompter = async (context, prompt, state, field) =>
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
await context.PostAsync(promptMessage);
return prompt;
};
Whereas the default prompter always posts promptMessage, your replacement can surround that line with an if statement. That leaves the question of what your condition should be. We've established that FormFlow doesn't include any concept of a retry, so you'd have to build that in yourself somehow. You could include a Boolean field as a switch in FAQConversation's state, or you could even use PrivateConversationData since the prompter gives you access to the DialogContext. You might think that would be a simple matter of turning off the switch when the prompt gets displayed once or when AnswerInquiryAsync determines that the user input is invalid, but then when would the switch get turned back on? What if the user enters "back" and you want the prompt to be displayed again?
While you might find some way to more accurately represent the logic of "disabling the prompt on retry," the simplest solution I came up with was to keep track of the last message FormFlow produced and then skip the first message that comes after "Try again." It looks like this:
[Serializable]
public class FAQConversation
{
public string Inquiry { get; set; }
private string LastMessage { get; set; }
private const string TRY_AGAIN = "Try again";
public static IForm<FAQConversation> BuildForm()
{
return new FormBuilder<FAQConversation>()
// This is an alternative way of using the Field() method but it works the same.
.Field(nameof(Inquiry),
"Okay, tell me what is your question. Enter \"back\" to go back to Products Selection.",
validate: AnswerInquiryAsync)
.Prompter(PromptAsync)
.Build();
}
private static async Task<ValidateResult> AnswerInquiryAsync(FAQConversation state, object value)
{
var result = new ValidateResult();
bool testCase = Equals(value, "true"); // Enter "true" to continue for testing purposes.
if (testCase == false)
{
result.IsValid = false;
// A constant should be used with strings that appear more than once in your code.
result.Feedback = TRY_AGAIN;
}
else
{
result.IsValid = true;
// A value must be provided or else the Field will not be populated.
result.Value = value;
}
return result;
}
/// <summary>
/// Here is the method we're using for the PromptAsyncDelegate.
/// </summary>
private static async Task<FormPrompt> PromptAsync(IDialogContext context, FormPrompt prompt,
FAQConversation state, IField<FAQConversation> field)
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
// Here is where we've made a change to the default prompter.
if (state.LastMessage != TRY_AGAIN)
{
await context.PostAsync(promptMessage);
}
state.LastMessage = promptMessage.Text;
return prompt;
}
}
I don't need any confirmation before completion of my form. However, in the following Build() method of the FormBuilder class there is a Confirm("Is this your selection?\n{}")*.
public IForm<T> Build()
{
if (!_form._steps.Any((step) => step.Type == StepType.Field))
{
var paths = new List<string>();
FormBuilder<T>.FieldPaths(typeof(T), "", paths);
IFormBuilder<T> builder = this;
foreach (var path in paths)
{
builder.Field(new FieldReflector<T>(path));
}
builder.Confirm("Is this your selection?\n{*}");
}
Validate();
return this._form;
}
Is there any way I can remove this step from my generated Form after calling build ?
var form = new FormBuilder<QuestionYourThinking>()
.OnCompletionAsync(async (context, state) =>
{
await context.PostAsync("L'exercice est maintenant terminé. A bientot !");
})
.Build();
Just use the overload that take an ActiveDelegate parameter and make the method handler to return false then the confirmation message will not be shown.
return new FormBuilder<QuestionYourThinking>()
.AddRemainingFields()
.Confirm("No verification will be shown", state => false)
.Message("L'exercice est maintenant terminé. A bientot !")
.Build();
To send a message you can just use the fluent method Message.
You can just use .AddRemainingFields() on your FormBuilder. It will not ask for any confirmation.
.Confirm should be used when you want to add custom confirmation message for any specific field.
I've got the following long winded code for doing a confirm dialog in WinRT
IAsyncOperation<IUICommand> asyncCommand = null;
var messageDialog = new MessageDialog("Are you sure you want to delete this file?", "Delete File");
// Add commands and set their callbacks
UICommand delete = new UICommand("Delete");
UICommand cancel = new UICommand("Cancel");
messageDialog.Commands.Add(delete);
messageDialog.Commands.Add(cancel);
messageDialog.DefaultCommandIndex = 1;
IUICommand response = await messageDialog.ShowAsync();
if (response == delete)
{
// delete file
}
Ok, it's not that long winded, but I'd love if there was some way to put it into a reusable method. This is what I have so far.
public void Confirm(String message, string title, string proceedButtonText, string cancelButtonText)
{
IAsyncOperation<IUICommand> asyncCommand = null;
var messageDialog = new MessageDialog(message, title);
// Add commands and set their callbacks
UICommand proceed = new UICommand(proceedButtonText);
UICommand cancel = new UICommand(cancelButtonText);
messageDialog.Commands.Add(proceed );
messageDialog.Commands.Add(cancel);
messageDialog.DefaultCommandIndex = 1;
IUICommand response = await messageDialog.ShowAsync();
if (response == proceed)
{
// how do I pass my function in here?
}
}
I can figure out passing the message, button names etc - but how do I pass my code / function for delete in there? I guess my question is how do I do a "callback" to run some code?
I've accepted kiewic's answer as he pointed me in the right direction. However, I thought I'd share the full code here as an answer, in case anyone else wants to use it.
private async Task<IUICommand> Confirm(string message, string title, UICommand proceed, UICommand cancel, uint defaultCommandIndex = 1)
{
var messageDialog = new MessageDialog(message, title);
// Add commands and set their callbacks
messageDialog.Commands.Add(proceed);
messageDialog.Commands.Add(cancel);
messageDialog.DefaultCommandIndex = defaultCommandIndex;
return await messageDialog.ShowAsync();
}
Call confirm like this:
await Confirm("Are you sure you want to delete this file?", "Delete File",
new UICommand("Delete", async (command) => {
// put your "delete" code here
}), new UICommand("Cancel"));
You could further extend this by passing it a collection of UICommand objects instead - this would allow dialogs with more than two options
While I was at it I also wrote an alert method which saves some code when displaying a messaging dialogue.
private async Task<IUICommand> Alert(string message, string title = "Error")
{
var messageDialog = new MessageDialog(message, title);
return await messageDialog.ShowAsync();
}
If you come from a web / js background like me, you might find these useful ;)
Use an Action delegate, check sample here: https://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx