Hexxed
Hexxed

Reputation: 683

How to prevent original prompt from showing on re-validation in a FormFlow?

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?

Upvotes: 1

Views: 187

Answers (1)

Kyle Delaney
Kyle Delaney

Reputation: 12284

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;
    }
}

Upvotes: 1

Related Questions