Spirotrack
Spirotrack

Reputation: 77

Change second dropdown items after selecting a value in the first one

I'm having trouble updating a second dropdown list when a value has been selected in the first one in ASP.NET Core Razor Pages.

I have the following Model:

public class OfferModel
{
    [BindProperty]
    public CreateOfferViewModel ViewModel { get; set; }
    public List<SelectListItem> ServiceTypes { get; set; }
    public List<SelectListItem> ServiceSubTypes { get; set; }

    private readonly IOfferAppService _offerAppService;

    public OfferModel(IOfferAppService service)
    {
        _offerAppService = service;
    }

    public virtual async Task OnGetAsync()
    {
        var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
        ServiceTypes = serviceTypeLookup.Items
            .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
            .ToList();
        var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync();
        ServiceSubTypes = serviceSubTypeLookup.Items
            .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
            .ToList();
    }
}

And the CreateOfferViewModel:

public class CreateOfferViewModel
{
    [SelectItems(nameof(ServiceTypes))]
    public Guid ServiceTypeId { get; set; }

    [SelectItems(nameof(ServiceSubTypes))]
    public Guid ServiceSubTypeId { get; set; }
}

And the View:

<select asp-for="ViewModel.ServiceSubTypeId" asp-items="Model.ServiceSubTypes"></select>
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes" ></select>

I have tried creating another method like so:

public virtual async Task OnGetSelectServiceTypeAsync(Guid serviceTypeId)
    {
        var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
        ServiceSubTypes = serviceSubTypeLookup.Items
                        .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
                        .ToList();
    }

And add a reference in the view to call it with jQuery:

<a asp-page-handler="SelectServiceType" asp-route-id="@Model.ViewModel.ServiceTypeId">Update</a>

But I get a NullReferenceException.

What should I do? What is the correct way of doing this?

Upvotes: 0

Views: 1909

Answers (1)

Rena
Rena

Reputation: 36715

But I get a NullReferenceException.

That is because you use asp-route-id="@Model.ViewModel.ServiceTypeId" but do not set any value to it.

There are two ways to meet your requirements.

Model:

public class CreateOfferViewModel
{
    public Guid ServiceTypeId { get; set; }
    public Guid ServiceSubTypeId { get; set; }
}
public class ServiceSubTypes
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    [ForeignKey("ServiceTypes")]
    public Guid ServiceTypeId { get; set; }
    public ServiceTypes ServiceTypes { get; set; }
}
public class ServiceTypes
{
    public Guid Id { get; set; }
    public string Name { get; set; }
   
    public List<ServiceSubTypes> ServiceSubTypes { get; set; }
}

The first way is to use ajax:

Index.cshtml:

@page
@model IndexModel
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes"></select>

<select asp-for="ViewModel.ServiceSubTypeId"></select>

@section Scripts
{
    <script>
        $(function () {
            $("#ViewModel_ServiceTypeId").on("change", function () {
                var ServiceTypeId = $(this).val();
                $("#ViewModel_ServiceSubTypeId").empty();
                $.getJSON(`?handler=SelectServiceType&serviceTypeId=${ServiceTypeId}`, (data) => {
                    $.each(data, function (i, item) {
                        console.log(data);
                        console.log(item);
                        $("#ViewModel_ServiceSubTypeId").append(`<option value="${item.id}">${item.name}</option>`);
                    });
                });
            });
        });

    </script>
}

Index.cshtml.cs:

public class IndexModel : PageModel
{
    [BindProperty]
    public CreateOfferViewModel ViewModel { get; set; }
    public List<SelectListItem> ServiceTypes { get; set; }
    public List<SelectListItem> ServiceSubTypes { get; set; }

    private readonly IOfferAppService _offerAppService;

    public IndexModel(IOfferAppService service)
    {
        _offerAppService = service;
    }

    public virtual async Task OnGetAsync()
    {
        var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
        ServiceTypes = serviceTypeLookup
            .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
            .ToList();
        var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync();
        ServiceSubTypes = serviceSubTypeLookup
            .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
            .ToList();
    }
    public virtual async Task<JsonResult> OnGetSelectServiceTypeAsync(Guid serviceTypeId)
    {
        var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
        
        return new JsonResult(serviceSubTypeLookup);
    }
}

Service:

public interface IOfferAppService
{
    Task<List<ServiceTypes>> GetServiceTypeLookupAsync();
    Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync();
    Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync(Guid id);
}
public class OfferApp : IOfferAppService
{
    private readonly RazorProj3_1Context _context;
    public OfferApp(RazorProj3_1Context context)
    {
        _context = context;
    }
    public async Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync()
    {
        var model =await _context.ServiceSubTypes.ToListAsync();
        return model;
    }

    public async Task<List<ServiceSubTypes>> GetServiceSubTypeLookupAsync(Guid id)
    {
        var model = await _context.ServiceSubTypes
                    .Where(a => a.ServiceTypeId == id).ToListAsync();
        return model;
    }

    public async Task<List<ServiceTypes>> GetServiceTypeLookupAsync()
    {
        var model = await _context.ServiceTypes.ToListAsync();
        return model;
    }
}

Result: enter image description here

The second way is like what you want to use jquery to add the value to url,but this way has a disadvantage,it would expose the URL in the browser address bar:

Index.cshtml:

@page
@model IndexModel
<select asp-for="ViewModel.ServiceTypeId" asp-items="Model.ServiceTypes"></select>
<select asp-for="ViewModel.ServiceSubTypeId" asp-items="Model.ServiceSubTypes"></select>

<a href="">Update</a>

@section Scripts
{
    <script>
        $(function () {
            $("#ViewModel_ServiceTypeId").on("change", function () {
                var ServiceTypeId = $(this).val();
                var url = $("a").attr("href", "?handler=SelectServiceType&serviceTypeId=" + ServiceTypeId);
            });
        });

    </script>
}

Index.cshtml.cs:

public class IndexModel : PageModel
{
    [BindProperty]
    public CreateOfferViewModel ViewModel { get; set; }
    public List<SelectListItem> ServiceTypes { get; set; }
    public List<SelectListItem> ServiceSubTypes { get; set; }

    private readonly IOfferAppService _offerAppService;

    public IndexModel(IOfferAppService service)
    {
        _offerAppService = service;
    }

    public virtual async Task OnGetSelectServiceTypeAsync(Guid serviceTypeId)
    {
        var serviceTypeLookup = await _offerAppService.GetServiceTypeLookupAsync();
        ServiceTypes = serviceTypeLookup
            .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
            .ToList();
        //obtain the first select item selected
        var data = ServiceTypes.Where(a => a.Value == serviceTypeId.ToString()).FirstOrDefault();
        data.Selected = true;
        var serviceSubTypeLookup = await _offerAppService.GetServiceSubTypeLookupAsync(serviceTypeId);
        ServiceSubTypes = serviceSubTypeLookup
                        .Select(x => new SelectListItem(x.Name, x.Id.ToString()))
                        .ToList();
        
    }
}

Result: enter image description here

Upvotes: 1

Related Questions