How to cascade DropDownList in Grid using ASP.NET Core Razor Pages?

2 Answers 293 Views
DropDownList Grid
Janko
Top achievements
Rank 1
Iron
Iron
Janko asked on 24 Aug 2023, 11:36 AM

How to cascade DropDownList in Grid?

I've tried a lot of stuff using JavaScript, JQuery and Ajax but I can't get this to work. I wasn't able to find an example on how to do this.

This is my code for grid and editor templates...

@(Html.Kendo().Grid<ToDestination>(Model.ToDestinations)
                .Name("grid")
                .Columns(columns =>
                {
                    columns.ForeignKey(c => c.TravelTypeId,
                    (System.Collections.IList)ViewData["TravelTypes"], "TravelTypeId", "TravelTypeName").Title("Travel Type");

                    columns.ForeignKey(c => c.CountryId,
                    (System.Collections.IList)ViewData["Countries"], "CountryId", "CountryName").Title("Country")
                    .EditorTemplateName("CountryEditor"); // Specify a custom editor template for the Country dropdown.

                    columns.ForeignKey(c => c.PlaceId,
                    (System.Collections.IList)ViewData["Places"], "PlaceId", "PlaceName").Title("Place")
                    .EditorTemplateName("PlaceEditor"); // Specify a custom editor template for the Place dropdown.

                    columns.Bound(c => c.PlaceName);
                    columns.Bound(c => c.CenterName);
                    columns.Command(command => { command.Edit(); command.Destroy(); }).Width(250);
                })
                .ToolBar(toolbar => toolbar.Create())
                .Editable(editable => editable.Mode(GridEditMode.InLine))
                .Pageable()
                .Sortable()
                .Scrollable()
                .DataSource(ds => ds.Ajax()
                .Create(c => c.Url("/TravelOrders/Create?handler=Create").Data("forgeryToken"))
                .Read(r => r.Url("/TravelOrders/Create?handler=Read").Data("forgeryToken"))
                .Update(u => u.Url("/TravelOrders/Create?handler=Update").Data("forgeryToken"))
                .Destroy(d => d.Url("/TravelOrders/Create?handler=Destroy").Data("forgeryToken"))
                .Model(m =>
                {
                    m.Id(id => id.ToDestinationId);
                    m.Field(tt => tt.TravelTypeId).DefaultValue(1);
                    m.Field(c => c.CountryId).DefaultValue(1);
                })
                )
                )

@model int? // Assuming CountryId is nullable

@(Html.Kendo().DropDownList()
    .Name("CountriesList")
    .DataTextField("CountryName")
    .DataValueField("CountryId")
    .BindTo((System.Collections.IEnumerable)ViewData["Countries"])
    .OptionLabel("Select a country")
    .Events(e => e.Change("onCountryChange"))
)

@model int? // Assuming PlaceId is nullable

@(Html.Kendo().DropDownList()
    .Name("PlacesList")
    .DataTextField("PlaceName")
    .DataValueField("PlaceId")
    .BindTo((System.Collections.IEnumerable)ViewData["Places"])
    .OptionLabel("Select a place")
)

2 Answers, 1 is accepted

Sort by
1
Accepted
Alexander
Telerik team
answered on 29 Aug 2023, 06:24 AM

Hi Janko,

Thank you for sharing the relevant code portion that is currently held on your premises, I really appreciate it.

To be frank, it is really impressive that you have managed to simulate a cascading functionality based on your own implementation. Generally, the Telerik UI for ASP.NET Core DropDownList provides built-in cascading capabilities that can be incorporated as well.

To give you more context, the functionality suffices the following actions:

  • It is checked whether the CascadeFrom property is set. If not, cascading is disabled.
  • The DropDownList tries to find the parent DropDownList object. If the result is null, then the functionality is omitted.
  • The child DropDownList listens to any changes in the parent value. If the parent does not have value, the child is disabled. If the parent has a value, the child is enabled and filters its data accordingly.

For more information, I would recommend reviewing the following resources as well:

Cascading DropDownList - (Documentation)

In the context of your scenario, you can utilize the server editor boolean overload to explicitly define a DropDownList incarnation of your own embedded within external editor templates. Here is an example:

.Columns(columns =>
{
    columns.ForeignKey(p => p.Fk_CountryID, ds => ds.Read(read => read.Url(Url.Page("Index", "Countries"))), "ID", "Description", true)
      .Title("Country").EditorTemplateName("CountryGridForeignKey").Width(200);

    columns.ForeignKey(p => p.Fk_StateID, ds => ds.Read(read => read.Url(Url.Page("Index", "StateLookup"))), "ID", "Description", true)
    .Title("State").EditorTemplateName("StateGridForeignKey").Width(200);
})

"Notice, that this approach would require loading the ForeignKey column data through an external endpoint instead of through the ViewData dictionary".

Here is what the editors would look like:

Parent DropDownList Editor:

@model int?

@(Html.Kendo().DropDownListFor(m => m)
    .ValuePrimitive(true)
    .OptionLabel("Select Country..")
    .DataValueField("ID")
    .DataTextField("Description")
    .DataSource(ds => ds.Read(read => read.Url(Url.Page("Index", "Countries"))))
)

Child DropDownList Editor:

@model int?

@(Html.Kendo().DropDownListFor(m => m)
    .AutoBind(false)
    .ValuePrimitive(true)
    .OptionLabel("Select State..")
    .DataValueField("ID")
    .DataTextField("Description")
    .DataSource(dataSource =>
     {
         dataSource.Read(read => read.Url(Url.Page("Index", "StateLookup")).Data("filterStates"))
                 .ServerFiltering(true);
     })
    .CascadeFrom("Fk_CountryID")
)

The child DropDownList will then be filtered through the "filterStates" handler:

<script>
    function filterStates() {
        console.log("here");
        return {
            countryId: $("#Fk_CountryID").data("kendoDropDownList").value()
        };
    }
</script>

Which will have the following model definition which will have a parent field as the proprietary field for filtering:

public class StateModel
{
    public string ID { get; set; }
    public string Description { get; set; }
    public int CountryID { get; set; }
}

This will then produce the following visual incarnation:

For your convenience, I am also attaching a runnable sample application that tackles the abovementioned scenario for you to review additionally.

I hope this information helps give a different perspective as to how the desired outcome can be achieved in a different manner.

Kind Regards,
Alexander
Progress Telerik

Stay tuned by visiting our public roadmap and feedback portal pages. If you're new to the Telerik family, be sure to check out our getting started resources, as well as the only REPL playground for creating, saving, running, and sharing server-side code.
0
Janko
Top achievements
Rank 1
Iron
Iron
answered on 28 Aug 2023, 01:33 PM

The solution to this is to have an id so that you can manage DropDownList values.

I've updated DropDownList codes and added script that handles event when country is changed.


@(Html.Kendo().Grid<ToDestination>(Model.ToDestinations)
            .Name("grid")
            .Columns(columns =>
            {
                columns.ForeignKey(c => c.TravelTypeId,
                (System.Collections.IList)ViewData["TravelTypes"], "TravelTypeId", "TravelTypeName").Title("Travel Type");

                columns.ForeignKey(c => c.CountryId,
                (System.Collections.IList)ViewData["Countries"], "CountryId", "CountryName").Title("Country")
                .EditorTemplateName("CountryEditor"); // Specify a custom editor template for the Country dropdown.

                columns.ForeignKey(c => c.PlaceId,
                (System.Collections.IList)ViewData["Places"], "PlaceId", "PlaceName").Title("Place")
                .EditorTemplateName("PlaceEditor"); // Specify a custom editor template for the Place dropdown.

                columns.Bound(c => c.PlaceName);
                columns.Bound(c => c.CenterName);
                columns.Command(command => { command.Edit(); command.Destroy(); }).Width(250);
            })
            .ToolBar(toolbar => toolbar.Create())
            .Editable(editable => editable.Mode(GridEditMode.InLine))
            .Pageable()
            .Sortable()
            .Scrollable()
            .DataSource(ds => ds.Ajax()
            .Create(c => c.Url("/TravelOrders/Create?handler=Create").Data("forgeryToken"))
            .Read(r => r.Url("/TravelOrders/Create?handler=Read").Data("forgeryToken"))
            .Update(u => u.Url("/TravelOrders/Create?handler=Update").Data("forgeryToken"))
            .Destroy(d => d.Url("/TravelOrders/Create?handler=Destroy").Data("forgeryToken"))
            .Model(m =>
            {
                m.Id(id => id.ToDestinationId);
                m.Field(tt => tt.TravelTypeId).DefaultValue(1);
                m.Field(c => c.CountryId).DefaultValue(1);
            })
            ))

@model int

@(
Html.Kendo().DropDownList()
    .Name("CountriesList")
    .DataTextField("CountryName")
    .DataValueField("CountryId")
    .BindTo((System.Collections.IEnumerable)ViewData["Countries"])
    .OptionLabel("Select a country")
    .Events(e => e.Change("onCountryChange"))
    .HtmlAttributes(new { id = "CountriesList" }) // Add an Id
)

@model int

@(Html.Kendo().DropDownList()
    .Name("PlacesList")
    .DataTextField("PlaceName")
    .DataValueField("PlaceId")
    .BindTo((System.Collections.IEnumerable)ViewData["Places"])
    .OptionLabel("Select a place")
    .HtmlAttributes(new { id = "PlacesList" }) // Add an Id
)

function onCountryChange(e) {
    var selectedCountryId = e.sender.value();
    refreshPlaces(selectedCountryId);
}

function refreshPlaces(selectedCountryId) {
    $.ajax({
        type: "GET",
        url: "/TravelOrders/Create?handler=RefreshPlaces",
        data: { countryId: selectedCountryId },
        success: function (data) {
            var placeDropdown = $("#PlacesList").data("kendoDropDownList");
            placeDropdown.setDataSource(new kendo.data.DataSource({ data: data }));
        },
        error: function (error) {
            console.error(error);
        }
    });
}

[HttpGet]
        public IActionResult OnGetRefreshPlaces (int countryId)
        {
            var places = _entities.Places.Where(p => p.CountryId == countryId).ToList();
            ViewData["Places"] = places;
            return new JsonResult(places);
        }

Tags
DropDownList Grid
Asked by
Janko
Top achievements
Rank 1
Iron
Iron
Answers by
Alexander
Telerik team
Janko
Top achievements
Rank 1
Iron
Iron
Share this question
or