Search code examples
c#botframeworkformflow

Customizing the Navigation Form after my form's confirmation dialog


I've defined a custom MessageDelegate to pass to .Confirm(...) on my FormBuilder. (See screenshot.)

My issue is that I'd like to customize the Navigation menu that appears when a user selects "No" on the Confirm dialog. I found this SO post which seems to be heading in the right direction, but I want more customization. I still want the list of buttons to appear, but I want to be able to specify which buttons appear/don't appear, as well as the text on each button, rather than having it auto-populated by FormFlow.

For example:

  • In my use case, I have a HasMiddleName field followed by a MiddleName field that only appears to the user if the HasMiddleName field receives a "Yes" answer. I want the Navigation to only show "Middle Name" similar to how it shows First/Last. And if the user selects the Middle Name, I want it to redirect to the HasMiddleName part of the form.

  • Another tweak is that I'd like to be able to format the Date of Birth to show only MM/dd/yyyy.

I tried to play around with using the Pattern language, but couldn't get it work... Is what I want possible? If I manually create the dialog I'd like to show how can I associate that with the FormFlow's navigation?

Custom Confirm and Subsequent Navigation Menu


Solution

  • The navigation step is a bit tricky to customize, so I've come up with a solution that lets you work around that. The trick is to make sure the MiddleName field is inactive when the navigation step rolls around and to have the HasMiddleName field masquerade as the MiddleName field so that clicking on it will take you to the HasMiddleName field.

    // We want our HasMiddleName field to be treated as the "Middle Name" field for navigation purposes
    [Describe("Middle Name"), Prompt("Does the dependent have a middle name?"), Template(TemplateUsage.NavigationFormat, "{&}({MiddleName})", FieldCase = CaseNormalization.None)]
    public bool HasMiddleName { get; set; }
    
    // I'm showing you how to use the "Unspecified" template but for some reason it doesn't work in the navigation step.
    // Also, be careful about giving two fields the same description. It works in this case because of the tricks we're using.
    [Optional, Describe("Middle Name"), Prompt("Please enter middle name {||}"), Template(TemplateUsage.NoPreference, "None"), Template(TemplateUsage.Unspecified, "None")]
    public string MiddleName { get; set; }
    
    [Template(TemplateUsage.NavigationFormat, "{&}({:d})", FieldCase = CaseNormalization.None)]
    public DateTime DateOfBirth { get; set; }
    
    public static IForm<MyClass> BuildForm()
    {
        var builder = new FormBuilder<MyClass>()
            .Field(new FieldReflector<MyClass>(nameof(HasMiddleName)).SetNext((value, state) =>
            {
                // This NextDelegate will execute after the user enters a value for HasMiddleName
                bool didTheySayYes = (bool)value;
                // If MiddleName is inactive it will be skipped over
                state._isMiddleNameActive = didTheySayYes;
    
                if (didTheySayYes)
                {
                    // We need to explicitly navigate to the MiddleName field
                    // or else it will go back to the confirmation step
                    // if a middle name had already been entered
                    return new NextStep(new[] { nameof(MiddleName) });
                }
                else
                {
                    // We want to clear the middle name in case one had been entered before
                    state.MiddleName = null;
                    // This will go to either the DateOfBirth field or the confirmation step
                    // since the MiddleName field will be inactive in this case
                    return new NextStep();
                }
            }))
            .Field(new FieldReflector<MyClass>(nameof(MiddleName)).SetActive(state => state._isMiddleNameActive))
            .Field(new FieldReflector<MyClass>(nameof(DateOfBirth)))
            .Confirm(async state =>
            {
                // We're making sure MiddleName is inactive at the confirmation step
                // so it won't be visible in the navigation step,
                // but since we're not changing the MiddleName field
                // it can still be retrieved from the form's result
                state._isMiddleNameActive = false;
                return new PromptAttribute("Ok. Is this correct? {||}");
            });
    
        return builder.Build();
    }
    
    // This private field isn't included in the form
    private bool _isMiddleNameActive;
    

    On the other hand, there's another avenue to explore if you really want control over what buttons show up in the navigation step. The above code should serve you well but I feel like I should briefly mention this other trick because I wrote a post about how to do it. If you make your own prompter, you can check if the field name equals __navigation__ in your PromptAsync delegate and then generate a message accordingly. Keep in mind that you'd still have to use some of the first solution to make sure clicking on "Middle Name" takes you to the HasMiddleName field.