How can I create a cascading dropdown in razor pages using predefined select lists?

509 Views Asked by At

I know this question has been asked quite a lot on here, but most of those questions are based on MVC and use interfaces and controllers in which im not very familiar with. And the other ones deal with JSON which im not sure is what im needing.

My end goal is to have a dropdown list show a certain select list based on what was chosen in the first list. I quickly found there was no easy way to just change the "asp-items" tag in the select.

After more than a day of trying multiple posts, I've ended up extremely confused.

Also database has been seeded successfully and the select lists do populate when tested in a single dropdown. so I know its not that part.

What I've tried: I've tried to implement multiple different answers from other posts using jQuery, regular JavaScript, and ajax. I also even tried to make 3 different dropdowns pre-populated with the list and just show and hide them based the the first selections. I was unable to get that to work and also found in another post that doing that would cause values to still be selected even though its not visible. This problem is quickly draining my sanity and it seems so trivial but i cant seem to get it. I don't fully understand jQuery or ajax either so that doesn't help me very much either.

I've tried versions almost everything I've found but my most recent attempt looks like this:

Index.cshtml.cs

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly Food2UContext _context;

    //First dropdowns options
    public static IEnumerable<SelectListItem> userTypeOptions() {
        return new[]
        {
            new SelectListItem {Text = "Customer", Value = "Shoppers"},
            new SelectListItem {Text = "Delivery Driver", Value = "DeliverPerson"},
            new SelectListItem {Text = "Restaurant", Value = "LocalRestaurants"},
        };
    } 

    //list to stores db object queries
    public List<Shoppers> shopper {get; set;} = default!;
    public List<DeliveryPerson> deliveryPeople {get; set;} = default!;
    public List<LocalRestaurants> restaurants {get; set;} = default!;

    //select lists to store each object type
    public SelectList shopperList {get; set;} = default!;
    public SelectList deliveryDriverList {get; set;} = default!;
    public SelectList restaurantList {get; set;} = default!;

    //select list to store the select list type chosen
    public SelectList displayedDropDown {get; set;} = default!;


    //called to grab select list depending on the first value selected
    public IActionResult OnGetFetchUserNames(string userType)
    {
        switch (userType)
        {
            case "Shoppers":
                displayedDropDown = shopperList;
                break;
            case "DeliverPerson":
                displayedDropDown = deliveryDriverList;
                break;
            case "LocalRestaurants":
                displayedDropDown = restaurantList;
                break;
        }
        
        //Tried json here because i saw another post successfully use it. 
        return new JsonResult(displayedDropDown);
    }

    public IndexModel(ILogger<IndexModel> logger, Food2UContext context)
    {
        _logger = logger;
        _context = context; //grab db context
    }

    //attempt to populate dropdowns and objects when page loads
    public void OnGet()
    {
        shopper = _context.Shoppers.ToList();
        deliveryPeople = _context.DeliverPerson.ToList();
        restaurants = _context.LocalRestaurants.ToList();

        shopperList = new SelectList(shopper, "shopperID", "Name");
        deliveryDriverList = new SelectList(deliveryPeople, "driverID", "Name");
        restaurantList = new SelectList(restaurants, "restaurantID", "Name");
    }
}

Index.cshtml

<form method="post">
        <div class="form-group w-25 mx-auto">
            <select id="userTypeDropDown" asp-items="IndexModel.userTypeOptions()" class="form-control">
                <option value="">-- Select User Type --</option>
            </select>
        </div>

        <div class="form-group w-25 mx-auto">
            <select id="userNameDropdown" name="users" asp-items="null" class="form-control">
                <option value="">-- Select Name --</option>
            </select>
        </div>
        
        <div class="form-group mt-3 text-center">
            <input type="submit" value="Continue" class="btn btn-secondary">
        </div>
    </form>

</div>

@*attempt to use ajax with the ongetfetch method from index.cshtml.cs*@
@section scripts{ 
<script>
    $("#userTypeDropDown").on("change", function () {
        var userType = $(this).val();
        $.ajax({
            method: 'get',
            url: '/Index?handler=FetchUserNames',
            data: {userType: userType },
            success: function (res) {
                $("#userNameDropdown").empty();
                var htmlString = "";
                $.each(res, function (k, v) {
                    htmlString += "<option value='" + v.val + "'>" + v.val + "</option>";
                });
                $("#userNameDropdown").append(htmlString);
            }

        })
    })
</script>
}
2

There are 2 best solutions below

1
Qing Guo On BEST ANSWER

First, we should add shopper andshopperList in public IndexModel(ILogger<IndexModel> logger, Food2UContext context), so that we can get the value agian when we call OnGetFetchUserNames.

try to change the code like:

public class IndexModel : PageModel
    {
        private readonly ILogger<IndexModel> _logger;
        private readonly Food2UContext _context;
        //list to stores db object queries
        public List<Shoppers> shopper {get; set;} = default!;
        public List<DeliveryPerson> deliveryPeople {get; set;} = default!;
        public List<LocalRestaurants> restaurants {get; set;} = default!;
    
        //select lists to store each object type
        public SelectList shopperList {get; set;} = default!;
        public SelectList deliveryDriverList {get; set;} = default!;
        public SelectList restaurantList {get; set;} = default!;
    
        //select list to store the select list type chosen
        public SelectList displayedDropDown {get; set;} = default!;
        //First dropdowns options
        public static IEnumerable<SelectListItem> userTypeOptions() {
            return new[]
            {
                new SelectListItem {Text = "Customer", Value = "Shoppers"},
                new SelectListItem {Text = "Delivery Driver", Value = "DeliverPerson"},
                new SelectListItem {Text = "Restaurant", Value = "LocalRestaurants"},
            };
        } 
 
        //called to grab select list depending on the first value selected
        public IActionResult OnGetFetchUserNames(string userType)
        {
            switch (userType)
            {
                case "Shoppers":
                    displayedDropDown = shopperList;
                    break;
                case "DeliverPerson":
                    displayedDropDown = deliveryDriverList;
                    break;
                case "LocalRestaurants":
                    displayedDropDown = restaurantList;
                    break;
            }
            
            //Tried json here because i saw another post successfully use it. 
            return new JsonResult(displayedDropDown);
        }
    
        public IndexModel(ILogger<IndexModel> logger, Food2UContext context)
        {
            _logger = logger;
            _context = context; //grab db context
            shopper = _context.Shoppers.ToList();
            deliveryPeople = _context.DeliverPerson.ToList();
            restaurants = _context.LocalRestaurants.ToList();
    
            shopperList = new SelectList(shopper, "shopperID", "Name");
            deliveryDriverList = new SelectList(deliveryPeople, "driverID", "Name");
            restaurantList = new SelectList(restaurants, "restaurantID", "Name");
        }
        }
    
        //attempt to populate dropdowns and objects when page loads
        public void OnGet()
        {
            
        }

Second: Define the htmlString like

htmlString += "<option value='" + v.value + "'>" + v.text + "</option>"; 

result: enter image description here

0
J D On

Many thanks to Qing Guo as they provided the correct answer but I would like to also answer with a rough work around I was able to implement until the solution was found in case it helps with similar situations.

Instead of populating the two dropdowns in one place, I was able to avoid using Jquery or Ajax all together with the following:

Index.cshtml.cs

    public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly Food2UDbContext _context;

    public IndexModel(ILogger<IndexModel> logger, Food2UDbContext context)
    {
        _logger = logger;
        _context = context; //grab db context
    }

    //First dropdowns options
    public static IEnumerable<SelectListItem> userTypeOptions() {
        return new[]
        {
            new SelectListItem {Text = "Customer", Value = "Shoppers"},
            new SelectListItem {Text = "Delivery Driver", Value = "DeliverPerson"},
            new SelectListItem {Text = "Restaurant", Value = "LocalRestaurants"},
        };
    } 

    //list to stores db object queries
    public List<Shoppers> shopper {get; set;} = default!;
    public List<DeliveryPerson> deliveryPeople {get; set;} = default!;
    public List<LocalRestaurants> restaurants {get; set;} = default!;

    //select list to store the select list type chosen
    public SelectList? displayedDropDown {get; set;} = default!;

    //Temporary properties to binded to html to get values
    [BindProperty(SupportsGet = true)]
    public string? SelectedUserType {get; set;}

    [BindProperty(SupportsGet = true)]
    public int? SelectedUserID {get; set;}

    //Properties to remember selected temporary values - store selected in OnGet and OnPost method
    public string? UserType {get; set;}
    public int? UserID {get; set;}

    //called to select list (dropdown) of users depending on the first value selected
    public IActionResult OnGet()
        {
            //populate each dropdown type from db
            shopper = _context.Shoppers.ToList();
            deliveryPeople = _context.DeliveryPerson.ToList();
            restaurants = _context.LocalRestaurants.ToList();

            //save selected userid for when page changes
            if (SelectedUserType != null)
            {
                UserType = SelectedUserType;
            }

            //Switch statement to set dropdown users depending on selected type
            if (UserType != null) 
            {
                switch (UserType) //sort posts - date desc is defaulted
                {
                case "Shoppers":
                    displayedDropDown = new SelectList(shopper, "shoppersID", "Name");
                    break;
                case "DeliverPerson":
                    displayedDropDown = new SelectList(deliveryPeople, "deliverypersonID", "Name");
                    break;
                case "LocalRestaurants":
                    displayedDropDown =  new SelectList(restaurants, "localrestaurantsID", "Name");
                    break;
                default:
                    displayedDropDown = null;
                    break;

                }
            }

            return Page();
        }
    
    //Post values and reroute depending on selected values
    public IActionResult OnPost()
    {
        //store usertype if not null
        if (SelectedUserType != null)
        {
            UserType = SelectedUserType;
        }

        //log usertype to see whats passed
        _logger.LogWarning(UserType);

        //store userid if not null
        if (SelectedUserID != null)
            {
                UserID = SelectedUserID;
            }

        //log userid if not null
        _logger.LogWarning(UserID.ToString());

        //if usertype is shoppers and userid is populated then route to chooseitems.cshtml page and pass variables to use if needed
        if (UserType == "Shoppers" && UserID != null)
                {
                    return RedirectToPage("./Chooseitems", new {userId = UserID, userType = UserType});
                }
        
        //return new Index page if routing criteria not met to start over
        return RedirectToPage("./Index");
    }
}

Index.cshtml

<div class="text-center">
    <h1 class="display-4">Welcome to Food2U</h1>
    <p>Please Login to your account!</p>
    @* Create Login Box *@
    @* button on-submit that then takes them to the local restaurant page *@

    <br>

    <form method="get">
        
        @if (Model.UserType == null)
        {
            <div class="form-group w-25 mx-auto">
                <select id="userTypeDropDown" asp-for="SelectedUserType" asp-items="IndexModel.userTypeOptions()" class="form-control">
                    <option value="">-- Select User Type --</option>
                </select>
            </div>

            <div class="form-group mt-3 text-center">
                <input asp-route-userType="@Model.SelectedUserType" type="submit" value="Continue" class="btn btn-secondary">
            </div>
        }

    </form>

    <form method="post">

        @if (Model.UserType != null) {
            <div class="form-group w-25 mx-auto">
                <select asp-for="SelectedUserID" id="userNameDropDown" asp-items="Model.displayedDropDown" class="form-control">
                    <option value="">-- Select Username --</option>
                </select>
            </div>

            <input type="hidden" asp-for="SelectedUserType"/>

            <div class="form-group mt-3 text-center">
            <input type="submit" value="Login" class="btn btn-secondary">
        </div>
        }

    </form>

</div>

What this does is show the first dropdown by itself with a continue button. After selecting the first dropdown value, the value is passed back through to the onget method using asp-for tags and the properties selectionUserType and UserType. SelectionUserType is a temporary variable that gets erased everytime the page loads so we pass it to UserType to save even after it reloads.

Then, when the page reloads, if UserType is not null (meaning it was selected) the logic in index.cshtml ignores generating the first dropdown and moves to generating the second dropdown which will be populated by whichever selectlist is chosen in the switch statement in the onget method.

After that, the selectedUserName is passed back to the on post method. and the two properties (UserType and UserName) are used in the post logic to reroute and, in my case, simulate the login.

It then looks like this

enter image description here

For some reason the screen capture doesnt show the drop down itself but it is there

Keep in mind, Alot more logic is needed to make this run smoothly which can make it more error prone. However, this is very similar to how you would implement a search bar and reload the page on search.

Hope this helps someone and thanks again to Qing Guo for the better solution