I am using Microsoft Bot Framework with a FormFlow to let the user fill in a form. Let's say my form has two fields:
So I have two enums:
public enum Country {
France, Germany
}
public enum City {
Paris, Berlin
}
The field City always depends on the field Country, because a city belongs to a country. That means the user should only be able to fill in either France+Paris or Germany+Berlin.
My form:
public class LocationForm
{
[Prompt("Which country? {||}")]
public Country? Country;
[Prompt("Which city? {||}")]
public City? City;
}
My builder:
private static IForm<LocationForm> BuildLocationForm()
{
return new FormBuilder<LocationForm>()
.Field(nameof(Country))
.Field(new FieldReflector<LocationForm>(nameof(LocationForm.City))
.SetDefine(async (state, field) =>
{
switch (state.Country)
{
case Country.France:
field.RemoveValue(City.Berlin);
break;
case Country.Germany:
field.RemoveValue(City.Paris);
break;
}
return true;
})
.Confirm(async (state) =>
new PromptAttribute(
"You selected {Country} and {City}, is that correct? {||}"))
.Build();
}
I don't know if the usage of RemoveValue is correct here, feels a bit hacked, but it works so far.
The first time the user fills out the form everything works fine, the user can only select either Paris or Berlin depending on the Country selected before. However, when the user answers the confirmation question with No it is possible to change either Country or City. When the user then changes the Country from lets say France to Germany the FormFlow will ask:
You selected Germany and Paris, is that correct?
Which is obviously not correct, but it is still possible to answer Yes.
I want to achieve, that, whenever the Country is being changed via the confirmation dialog, the user has to change the City selection depending on the change in the Country field aswell.
I was playing around with the SetNext method, but I couldn't figure out anything useful.
Am I on the wrong track here? Is that even possible without hacking around the whole FormFlow implementation?
I would appreciate any help!
Thanks in advance!
Update (Additional Info): I tried the Sandwich Bot example given by Microsoft found here and it seems to have the same (mis)behavior. You get an extra (Free cookie/drink) when ordering a Foot Long sandwich. Changing the length to Six Inch after the confirmation you still have your extra, but only pay for Six Inch.
What you can do is, inside the country
field, validate if previous and new selections are same; in case its not clear the value of city
so that city is prompted again.
Also change your city type to string
, so that you can dynamically load the values based on selection of country
instead of using field.RemoveValue()
(Though that is not a bad approach)
So the code would be:
[Serializable]
public class LocationForm
{
[Prompt("Which country? {||}")]
public Country? country;
[Prompt("Which city? {||}")]
public string city;
public static IForm<LocationForm> BuildLocationForm()
{
return new FormBuilder<LocationForm>()
.Field(new FieldReflector<LocationForm>(nameof(country))
.SetValidate(async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
//Validation to check if the current country and previous selected country are same so that user is not prompted again for city
if (state.country.ToString() != response.ToString())
state.city = String.Empty;
return result;
}))
.Field(new FieldReflector<LocationForm>(nameof(city))
//To get buttons SetType(null)
.SetType(null)
.SetDefine(async (state, field) =>
{
//Any previous value before the confirm should be cleared in case selection is changed for country
field.RemoveValues();
switch (state.country)
{
case Country.France:
field
.AddDescription(nameof(City.Berlin), nameof(City.Berlin))
.AddTerms(nameof(City.Berlin), nameof(City.Berlin));
//Add more description and terms if any
break;
case Country.Germany:
field
.AddDescription(nameof(City.Paris), nameof(City.Paris))
.AddTerms(nameof(City.Paris), nameof(City.Paris));
//Add more description and terms if any
break;
}
return true;
}))
.AddRemainingFields()
.Confirm(async (state) =>
{
return new PromptAttribute($"You selected {state.country} and {state.city}, is that correct?" + "{||}");
})
.Build();
}
}
[Serializable]
public enum Country
{
France, Germany
}
[Serializable]
public enum City
{
Paris, Berlin
}
Now you have the expected behavior. In case the country is changed, the city will be prompted again for selection. In case the user says no
for confirmation and doesn't change the country and selects the previous selection of county itself then city is not prompted again, a direct confirmation of selected Country
and City
is prompted.